mirror of
https://github.com/cline/cline.git
synced 2026-09-06 12:28:08 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ead62eab65 | |||
| 7571506c78 | |||
| 0bd935f683 |
+9
-19
@@ -30,14 +30,11 @@
|
||||
"correctness": {
|
||||
"useExhaustiveDependencies": "off",
|
||||
"noUndeclaredVariables": "off",
|
||||
"noEmptyPattern": "off",
|
||||
"useJsxKeyInIterable": "off",
|
||||
"noInnerDeclarations": "off",
|
||||
"useHookAtTopLevel": "off",
|
||||
"useYield": "off",
|
||||
"noConstructorReturn": "off",
|
||||
"noInvalidPositionAtImportRule": "off",
|
||||
"noSwitchDeclarations": "off",
|
||||
"noUnusedImports": "error"
|
||||
},
|
||||
"a11y": "off",
|
||||
@@ -61,25 +58,18 @@
|
||||
"noUselessElse": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noDoubleEquals": "warn",
|
||||
"noImplicitAnyLet": "info",
|
||||
"noThenProperty": "off",
|
||||
"noAsyncPromiseExecutor": "off",
|
||||
"noImportAssign": "off",
|
||||
"noExplicitAny": "off",
|
||||
"noControlCharactersInRegex": "off",
|
||||
"noShadowRestrictedNames": "off",
|
||||
"noThenProperty": "warn",
|
||||
"noAsyncPromiseExecutor": "warn",
|
||||
"noExplicitAny": "warn",
|
||||
"noImportAssign": "warn",
|
||||
"noArrayIndexKey": "info",
|
||||
"noAssignInExpressions": "warn"
|
||||
"noAssignInExpressions": "warn",
|
||||
"noShadowRestrictedNames": "info",
|
||||
"noControlCharactersInRegex": "warn"
|
||||
},
|
||||
"complexity": {
|
||||
"noUselessConstructor": "off",
|
||||
"useOptionalChain": "off",
|
||||
"noBannedTypes": "off",
|
||||
"useLiteralKeys": "off",
|
||||
"noUselessCatch": "off",
|
||||
"noUselessSwitchCase": "off",
|
||||
"noStaticOnlyClass": "off"
|
||||
"useOptionalChain": "info",
|
||||
"useLiteralKeys": "off"
|
||||
},
|
||||
"security": {
|
||||
"noDangerouslySetInnerHtml": "warn"
|
||||
|
||||
@@ -200,7 +200,7 @@ async function checkProtos() {
|
||||
continue
|
||||
}
|
||||
// Check message fields
|
||||
if (def && def.type && def.type.field) {
|
||||
if (def?.type?.field) {
|
||||
for (const field of def.type.field) {
|
||||
if (int64TypeNames.includes(field.type)) {
|
||||
const name = `${packageName}.${messageName}.${field.name}`
|
||||
|
||||
@@ -66,7 +66,7 @@ async function main(): Promise<void> {
|
||||
}
|
||||
|
||||
try {
|
||||
const apiServer = await ClineApiServerMock.startGlobalServer()
|
||||
const _apiServer = await ClineApiServerMock.startGlobalServer()
|
||||
console.log("Cline API Server started in-process")
|
||||
} catch (error) {
|
||||
console.error("Failed to start Cline API Server:", error)
|
||||
|
||||
@@ -4,13 +4,11 @@ import { ApiHandler } from "../../core/api/index"
|
||||
import { ApiStream } from "../../core/api/transform/stream"
|
||||
|
||||
export class DifyHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private baseUrl: string
|
||||
private apiKey: string
|
||||
private conversationId: string | null = null
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.apiKey = options.difyApiKey || ""
|
||||
this.baseUrl = options.difyBaseUrl || ""
|
||||
|
||||
@@ -59,7 +57,7 @@ export class DifyHandler implements ApiHandler {
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
console.error("[DIFY DEBUG] Network error during fetch:", error)
|
||||
// Log more detailed error information if available (e.g., from undici)
|
||||
const cause = error.cause ? ` | Cause: ${error.cause}` : ""
|
||||
@@ -236,7 +234,7 @@ export class DifyHandler implements ApiHandler {
|
||||
console.error("[DIFY DEBUG] Direct JSON Error event:", parsed)
|
||||
throw new Error(`Dify API error: ${parsed.message || "Unknown error"}`)
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
// Not JSON, continue
|
||||
console.log("[DIFY DEBUG] Line is not direct JSON, continuing")
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk?.type) {
|
||||
case "message_start":
|
||||
case "message_start": {
|
||||
// tells us cache reads/writes/input/output
|
||||
const usage = chunk.message.usage
|
||||
yield {
|
||||
@@ -163,6 +163,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
cacheReadTokens: usage.cache_read_input_tokens || undefined,
|
||||
}
|
||||
break
|
||||
}
|
||||
case "message_delta":
|
||||
// tells us stop_reason, stop_sequence, and output tokens along the way and at the end of the message
|
||||
|
||||
|
||||
@@ -861,7 +861,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)
|
||||
|
||||
@@ -3,6 +3,14 @@ import { ApiHandlerOptions, ModelInfo } from "../../../shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface DifyRequestBody {
|
||||
rating?: "like" | "dislike"
|
||||
user: string
|
||||
auto_generate?: boolean
|
||||
name?: string
|
||||
content?: string
|
||||
}
|
||||
|
||||
// Dify API Response Types
|
||||
export interface DifyFileResponse {
|
||||
id: string
|
||||
@@ -66,15 +74,11 @@ interface DifyConversationResponse {
|
||||
}
|
||||
|
||||
export class DifyHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private baseUrl: string
|
||||
private apiKey: string
|
||||
private conversationId: string | null = null
|
||||
private currentTaskId: string | null = null
|
||||
private abortController: AbortController | null = null
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.apiKey = options.difyApiKey || ""
|
||||
this.baseUrl = options.difyBaseUrl || ""
|
||||
|
||||
@@ -122,7 +126,7 @@ export class DifyHandler implements ApiHandler {
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
console.error("[DIFY DEBUG] Network error during fetch:", error)
|
||||
const cause = error.cause ? ` | Cause: ${error.cause}` : ""
|
||||
throw new Error(`Dify API network error: ${error.message}${cause}`)
|
||||
@@ -336,7 +340,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
hasYieldedContent = true
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
// Not JSON, continue
|
||||
console.log("[DIFY DEBUG] Line is not direct JSON, continuing")
|
||||
}
|
||||
@@ -573,7 +577,7 @@ export class DifyHandler implements ApiHandler {
|
||||
name?: string,
|
||||
autoGenerate: boolean = false,
|
||||
): Promise<DifyConversationResponse> {
|
||||
const body: any = { user, auto_generate: autoGenerate }
|
||||
const body: DifyRequestBody = { user, auto_generate: autoGenerate }
|
||||
if (name) {
|
||||
body.name = name
|
||||
}
|
||||
@@ -609,7 +613,7 @@ export class DifyHandler implements ApiHandler {
|
||||
content?: string,
|
||||
user: string = "cline-user",
|
||||
): Promise<void> {
|
||||
const body: any = { rating, user }
|
||||
const body: DifyRequestBody = { rating, user }
|
||||
if (content) {
|
||||
body.content = content
|
||||
}
|
||||
@@ -650,6 +654,5 @@ export class DifyHandler implements ApiHandler {
|
||||
*/
|
||||
resetConversation(): void {
|
||||
this.conversationId = null
|
||||
this.currentTaskId = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -230,7 +230,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) {
|
||||
|
||||
@@ -66,47 +66,43 @@ export class HuggingFaceHandler implements ApiHandler {
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): 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,
|
||||
}
|
||||
const requestParams = {
|
||||
model: model.id,
|
||||
max_tokens: model.info.maxTokens,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
}
|
||||
|
||||
const stream = (await client.chat.completions.create(requestParams)) as any
|
||||
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 (chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw error
|
||||
|
||||
if (chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
}
|
||||
case "gpt-5-2025-08-07":
|
||||
case "gpt-5-mini-2025-08-07":
|
||||
case "gpt-5-nano-2025-08-07":
|
||||
case "gpt-5-nano-2025-08-07": {
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
temperature: 1,
|
||||
@@ -130,6 +130,7 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
const stream = await client.chat.completions.create({
|
||||
model: model.id,
|
||||
|
||||
@@ -163,7 +163,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)) {
|
||||
@@ -257,7 +257,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) {
|
||||
@@ -902,7 +902,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,
|
||||
|
||||
@@ -82,40 +82,66 @@ export class VertexHandler implements ApiHandler {
|
||||
(modelId.includes("3-7") || modelId.includes("sonnet-4") || modelId.includes("opus-4")) &&
|
||||
budget_tokens !== 0
|
||||
)
|
||||
let stream
|
||||
|
||||
switch (modelId) {
|
||||
case "claude-sonnet-4@20250514":
|
||||
case "claude-opus-4-1@20250805":
|
||||
case "claude-opus-4@20250514":
|
||||
case "claude-3-7-sonnet@20250219":
|
||||
case "claude-3-5-sonnet-v2@20241022":
|
||||
case "claude-3-5-sonnet@20240620":
|
||||
case "claude-3-5-haiku@20241022":
|
||||
case "claude-3-opus@20240229":
|
||||
case "claude-3-haiku@20240307": {
|
||||
// Find indices of user messages for cache control
|
||||
const userMsgIndices = messages.reduce(
|
||||
(acc, msg, index) => (msg.role === "user" ? [...acc, index] : acc),
|
||||
[] as number[],
|
||||
)
|
||||
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
|
||||
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
|
||||
stream = await clientAnthropic.beta.messages.create(
|
||||
{
|
||||
model: modelId,
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
thinking: reasoningOn ? { type: "enabled", budget_tokens: budget_tokens } : undefined,
|
||||
temperature: reasoningOn ? undefined : 0,
|
||||
system: [
|
||||
{
|
||||
text: systemPrompt,
|
||||
type: "text",
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
],
|
||||
messages: messages.map((message, index) => {
|
||||
if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
|
||||
function getStream() {
|
||||
switch (modelId) {
|
||||
case "claude-sonnet-4@20250514":
|
||||
case "claude-opus-4-1@20250805":
|
||||
case "claude-opus-4@20250514":
|
||||
case "claude-3-7-sonnet@20250219":
|
||||
case "claude-3-5-sonnet-v2@20241022":
|
||||
case "claude-3-5-sonnet@20240620":
|
||||
case "claude-3-5-haiku@20241022":
|
||||
case "claude-3-opus@20240229":
|
||||
case "claude-3-haiku@20240307": {
|
||||
// Find indices of user messages for cache control
|
||||
const userMsgIndices = messages.reduce((acc, msg, index) => {
|
||||
if (msg.role === "user") {
|
||||
acc.push(index)
|
||||
}
|
||||
return acc
|
||||
}, [] as number[])
|
||||
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
|
||||
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
|
||||
return clientAnthropic.beta.messages.create(
|
||||
{
|
||||
model: modelId,
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
thinking: reasoningOn ? { type: "enabled", budget_tokens: budget_tokens } : undefined,
|
||||
temperature: reasoningOn ? undefined : 0,
|
||||
system: [
|
||||
{
|
||||
text: systemPrompt,
|
||||
type: "text",
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
],
|
||||
messages: messages.map((message, index) => {
|
||||
if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
},
|
||||
]
|
||||
: message.content.map((content, contentIndex) =>
|
||||
contentIndex === message.content.length - 1
|
||||
? {
|
||||
...content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
}
|
||||
: content,
|
||||
),
|
||||
}
|
||||
}
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
@@ -124,76 +150,50 @@ export class VertexHandler implements ApiHandler {
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
},
|
||||
]
|
||||
: message.content.map((content, contentIndex) =>
|
||||
contentIndex === message.content.length - 1
|
||||
? {
|
||||
...content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
}
|
||||
: content,
|
||||
),
|
||||
: message.content,
|
||||
}
|
||||
}
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
},
|
||||
]
|
||||
: message.content,
|
||||
}
|
||||
}),
|
||||
stream: true,
|
||||
},
|
||||
{
|
||||
headers: {},
|
||||
},
|
||||
)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
stream = await clientAnthropic.beta.messages.create({
|
||||
model: modelId,
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
system: [
|
||||
{
|
||||
text: systemPrompt,
|
||||
type: "text",
|
||||
}),
|
||||
stream: true,
|
||||
},
|
||||
],
|
||||
messages: messages.map((message) => ({
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
},
|
||||
]
|
||||
: message.content,
|
||||
})),
|
||||
stream: true,
|
||||
})
|
||||
break
|
||||
{
|
||||
headers: {},
|
||||
},
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return clientAnthropic.beta.messages.create({
|
||||
model: modelId,
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
system: [
|
||||
{
|
||||
text: systemPrompt,
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
messages: messages.map((message) => ({
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
},
|
||||
]
|
||||
: message.content,
|
||||
})),
|
||||
stream: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for await (const chunk of stream) {
|
||||
for await (const chunk of await getStream()) {
|
||||
switch (chunk?.type) {
|
||||
case "message_start":
|
||||
case "message_start": {
|
||||
const usage = chunk.message.usage
|
||||
yield {
|
||||
type: "usage",
|
||||
@@ -203,6 +203,7 @@ export class VertexHandler implements ApiHandler {
|
||||
cacheReadTokens: usage.cache_read_input_tokens || undefined,
|
||||
}
|
||||
break
|
||||
}
|
||||
case "message_delta":
|
||||
yield {
|
||||
type: "usage",
|
||||
|
||||
@@ -388,7 +388,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
|
||||
}
|
||||
|
||||
@@ -173,7 +173,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
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function createOpenRouterStream(
|
||||
case "anthropic/claude-3-haiku":
|
||||
case "anthropic/claude-3-haiku:beta":
|
||||
case "anthropic/claude-3-opus":
|
||||
case "anthropic/claude-3-opus:beta":
|
||||
case "anthropic/claude-3-opus:beta": {
|
||||
openAiMessages[0] = {
|
||||
role: "system",
|
||||
content: [
|
||||
@@ -80,6 +80,7 @@ export async function createOpenRouterStream(
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -131,7 +132,7 @@ export async function createOpenRouterStream(
|
||||
case "anthropic/claude-3.7-sonnet:beta":
|
||||
case "anthropic/claude-3.7-sonnet:thinking":
|
||||
case "anthropic/claude-3-7-sonnet":
|
||||
case "anthropic/claude-3-7-sonnet:beta":
|
||||
case "anthropic/claude-3-7-sonnet:beta": {
|
||||
const budget_tokens = thinkingBudgetTokens || 0
|
||||
const reasoningOn = budget_tokens !== 0
|
||||
if (reasoningOn) {
|
||||
@@ -139,6 +140,7 @@ export async function createOpenRouterStream(
|
||||
reasoning = { max_tokens: budget_tokens }
|
||||
}
|
||||
break
|
||||
}
|
||||
case "cline/sonic":
|
||||
temperature = 0.7
|
||||
topP = 0.95
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function createVercelAIGatewayStream(
|
||||
// Find the last text part in the message content
|
||||
const lastTextPart = msg.content.filter((part) => part.type === "text").pop()
|
||||
|
||||
if (lastTextPart && lastTextPart.text && lastTextPart.text.length > 0) {
|
||||
if (lastTextPart?.text && lastTextPart.text.length > 0) {
|
||||
// @ts-ignore-next-line
|
||||
lastTextPart["cache_control"] = { type: "ephemeral" }
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class ContextManager {
|
||||
// If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
|
||||
if (previousApiReqIndex >= 0) {
|
||||
const previousRequest = clineMessages[previousApiReqIndex]
|
||||
if (previousRequest && previousRequest.text) {
|
||||
if (previousRequest?.text) {
|
||||
const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(previousRequest.text)
|
||||
const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
|
||||
const { maxAllowedSize } = getContextWindowInfo(api)
|
||||
|
||||
@@ -111,7 +111,7 @@ export class ContextManager {
|
||||
shouldCompactContextWindow(clineMessages: ClineMessage[], api: ApiHandler, previousApiReqIndex: number): boolean {
|
||||
if (previousApiReqIndex >= 0) {
|
||||
const previousRequest = clineMessages[previousApiReqIndex]
|
||||
if (previousRequest && previousRequest.text) {
|
||||
if (previousRequest?.text) {
|
||||
const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(previousRequest.text)
|
||||
const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
|
||||
|
||||
@@ -150,7 +150,7 @@ export class ContextManager {
|
||||
|
||||
if (targetIndex >= 0) {
|
||||
const targetRequest = clineMessages[targetIndex]
|
||||
if (targetRequest && targetRequest.text) {
|
||||
if (targetRequest?.text) {
|
||||
try {
|
||||
const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(targetRequest.text)
|
||||
const tokensUsed = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
|
||||
@@ -187,7 +187,7 @@ export class ContextManager {
|
||||
// If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
|
||||
if (previousApiReqIndex >= 0) {
|
||||
const previousRequest = clineMessages[previousApiReqIndex]
|
||||
if (previousRequest && previousRequest.text) {
|
||||
if (previousRequest?.text) {
|
||||
const timestamp = previousRequest.ts
|
||||
const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(previousRequest.text)
|
||||
const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
|
||||
|
||||
@@ -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] : [],
|
||||
}),
|
||||
) || [],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,16 +9,12 @@ 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)
|
||||
|
||||
return Empty.create({})
|
||||
} 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)
|
||||
|
||||
return Empty.create({})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ export class Controller {
|
||||
let apiKey: string
|
||||
try {
|
||||
const response = await axios.post("https://openrouter.ai/api/v1/auth/keys", { code })
|
||||
if (response.data && response.data.key) {
|
||||
if (response.data?.key) {
|
||||
apiKey = response.data.key
|
||||
} else {
|
||||
throw new Error("Invalid response from OpenRouter API")
|
||||
|
||||
@@ -94,7 +94,7 @@ async function fetchAiCoreModelsAndOrchestration(
|
||||
* @returns SapAiCoreModelsResponse with model names and orchestration availability
|
||||
*/
|
||||
export async function getSapAiCoreModels(
|
||||
controller: Controller,
|
||||
_controller: Controller,
|
||||
request: SapAiCoreModelsRequest,
|
||||
): Promise<SapAiCoreModelsResponse> {
|
||||
try {
|
||||
|
||||
@@ -81,7 +81,6 @@ export async function getTaskHistory(controller: Controller, request: GetTaskHis
|
||||
(b.cacheReads || 0) -
|
||||
((a.tokensIn || 0) + (a.tokensOut || 0) + (a.cacheWrites || 0) + (a.cacheReads || 0))
|
||||
)
|
||||
case "newest":
|
||||
default:
|
||||
return b.ts - a.ts
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function initializeWebview(controller: Controller, _request: EmptyR
|
||||
|
||||
// Refresh OpenRouter models from API
|
||||
refreshOpenRouterModels(controller, EmptyRequest.create()).then(async (response) => {
|
||||
if (response && response.models) {
|
||||
if (response?.models) {
|
||||
// Update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
||||
const apiConfiguration = controller.stateManager.getApiConfiguration()
|
||||
const planActSeparateModelsSetting = controller.stateManager.getGlobalStateKey("planActSeparateModelsSetting")
|
||||
@@ -74,7 +74,7 @@ export async function initializeWebview(controller: Controller, _request: EmptyR
|
||||
})
|
||||
|
||||
refreshGroqModels(controller, EmptyRequest.create()).then(async (response) => {
|
||||
if (response && response.models) {
|
||||
if (response?.models) {
|
||||
// Update model info in state for Groq (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
||||
const apiConfiguration = controller.stateManager.getApiConfiguration()
|
||||
const planActSeparateModelsSetting = controller.stateManager.getGlobalStateKey("planActSeparateModelsSetting")
|
||||
@@ -120,7 +120,7 @@ export async function initializeWebview(controller: Controller, _request: EmptyR
|
||||
})
|
||||
|
||||
refreshBasetenModels(controller, EmptyRequest.create()).then(async (response) => {
|
||||
if (response && response.models) {
|
||||
if (response?.models) {
|
||||
// Update model info in state for Baseten (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
||||
const apiConfiguration = controller.stateManager.getApiConfiguration()
|
||||
const planActSeparateModelsSetting = controller.stateManager.getGlobalStateKey("planActSeparateModelsSetting")
|
||||
@@ -162,7 +162,7 @@ export async function initializeWebview(controller: Controller, _request: EmptyR
|
||||
|
||||
// Refresh Vercel AI Gateway models from API
|
||||
refreshVercelAiGatewayModels(controller, EmptyRequest.create()).then(async (response) => {
|
||||
if (response && response.models) {
|
||||
if (response?.models) {
|
||||
// Update model info in state for Vercel AI Gateway (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
||||
const apiConfiguration = controller.stateManager.getApiConfiguration()
|
||||
const planActSeparateModelsSetting = controller.stateManager.getGlobalStateKey("planActSeparateModelsSetting")
|
||||
|
||||
@@ -80,15 +80,19 @@ const compareStrings = (expected: string, actual: string): string | null => {
|
||||
if (differences.length < 10) {
|
||||
// Limit to first 10 differences for readability
|
||||
differences.push(`Line ${i + 1}:`)
|
||||
if (expectedLine)
|
||||
if (expectedLine) {
|
||||
differences.push(` - Expected: ${expectedLine.substring(0, 100)}${expectedLine.length > 100 ? "..." : ""}`)
|
||||
if (actualLine)
|
||||
}
|
||||
if (actualLine) {
|
||||
differences.push(` + Actual: ${actualLine.substring(0, 100)}${actualLine.length > 100 ? "..." : ""}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (differences.length === 0) return null
|
||||
if (differences.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const summary = [
|
||||
`Expected length: ${expected.length} characters`,
|
||||
|
||||
@@ -94,7 +94,7 @@ export class PromptBuilder {
|
||||
.trim() // Remove leading/trailing whitespace
|
||||
.replace(/====+\s*$/, "") // Remove trailing ==== after trim
|
||||
.replace(/\n====+\s*\n+\s*====+\n/g, "\n====\n") // Remove empty sections between separators
|
||||
.replace(/====+\n(?!\n)([^\n])/g, (match, nextChar, offset, string) => {
|
||||
.replace(/====+\n(?!\n)([^\n])/g, (match, _nextChar, offset, string) => {
|
||||
// Add extra newline after ====+ if not already followed by a newline
|
||||
// Exception: preserve single newlines when ====+ appears to be part of diff-like content
|
||||
// Look for patterns like "SEARCH\n=======\n" or ";\n=======\n" (diff markers)
|
||||
|
||||
@@ -192,7 +192,7 @@ export class ToolExecutor {
|
||||
|
||||
this.coordinator.register(new ListCodeDefinitionNamesToolHandler(validator))
|
||||
this.coordinator.register(new SearchFilesToolHandler(validator))
|
||||
this.coordinator.register(new ExecuteCommandToolHandler(validator))
|
||||
this.coordinator.register(new ExecuteCommandToolHandler())
|
||||
this.coordinator.register(new UseMcpToolHandler())
|
||||
this.coordinator.register(new AccessMcpResourceHandler())
|
||||
this.coordinator.register(new LoadMcpDocumentationHandler())
|
||||
|
||||
@@ -389,12 +389,12 @@ ${listInstrunctionsReminder}\n`
|
||||
public async updateFCListFromToolResponse(taskProgress: string | undefined) {
|
||||
try {
|
||||
// Reset the counter if task_progress was provided
|
||||
if (taskProgress && taskProgress.trim()) {
|
||||
if (taskProgress?.trim()) {
|
||||
this.taskState.apiRequestsSinceLastTodoUpdate = 0
|
||||
}
|
||||
|
||||
// If model provides task_progress update, write it to the markdown file
|
||||
if (taskProgress && taskProgress.trim()) {
|
||||
if (taskProgress?.trim()) {
|
||||
const previousList = this.taskState.currentFocusChainChecklist
|
||||
this.taskState.currentFocusChainChecklist = taskProgress.trim()
|
||||
console.debug(
|
||||
|
||||
@@ -747,8 +747,7 @@ export class Task {
|
||||
const lastMessage = clineMessages.at(-1)
|
||||
const lastMessageIndex = clineMessages.length - 1
|
||||
|
||||
const isUpdatingPreviousPartial =
|
||||
lastMessage && lastMessage.partial && lastMessage.type === "ask" && lastMessage.ask === type
|
||||
const isUpdatingPreviousPartial = lastMessage?.partial && lastMessage.type === "ask" && lastMessage.ask === type
|
||||
if (partial) {
|
||||
if (isUpdatingPreviousPartial) {
|
||||
// existing partial message, so update it
|
||||
@@ -872,8 +871,7 @@ export class Task {
|
||||
|
||||
if (partial !== undefined) {
|
||||
const lastMessage = this.messageStateHandler.getClineMessages().at(-1)
|
||||
const isUpdatingPreviousPartial =
|
||||
lastMessage && lastMessage.partial && lastMessage.type === "say" && lastMessage.say === type
|
||||
const isUpdatingPreviousPartial = lastMessage?.partial && lastMessage.type === "say" && lastMessage.say === type
|
||||
if (partial) {
|
||||
if (isUpdatingPreviousPartial) {
|
||||
// existing partial message, so update it
|
||||
@@ -2284,7 +2282,7 @@ export class Task {
|
||||
|
||||
// if last message is a partial we need to update and save it
|
||||
const lastMessage = this.messageStateHandler.getClineMessages().at(-1)
|
||||
if (lastMessage && lastMessage.partial) {
|
||||
if (lastMessage?.partial) {
|
||||
// lastMessage.ts = Date.now() DO NOT update ts since it is used as a key for virtuoso list
|
||||
lastMessage.partial = false
|
||||
// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
|
||||
|
||||
@@ -58,7 +58,7 @@ async function getChangedFiles(
|
||||
lastCheckpointHash: string,
|
||||
): Promise<ChangedFile[]> {
|
||||
try {
|
||||
let changedFiles
|
||||
let changedFiles: ChangedFile[] | undefined
|
||||
if (changesSinceLastTaskCompletion) {
|
||||
changedFiles = await getChangesSinceLastTaskCompletion(
|
||||
messageStateHandler,
|
||||
|
||||
@@ -12,8 +12,6 @@ import { ToolResultUtils } from "../utils/ToolResultUtils"
|
||||
export class AccessMcpResourceHandler implements IFullyManagedTool {
|
||||
readonly name = "access_mcp_resource"
|
||||
|
||||
constructor() {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name} for '${block.params.server_name}']`
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ import { ToolResultUtils } from "../utils/ToolResultUtils"
|
||||
export class AttemptCompletionHandler implements IToolHandler, IPartialBlockHandler {
|
||||
readonly name = "attempt_completion"
|
||||
|
||||
constructor() {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name}]`
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ export class BrowserToolHandler implements IFullyManagedTool {
|
||||
case "click":
|
||||
case "type":
|
||||
case "scroll_down":
|
||||
case "scroll_up":
|
||||
case "scroll_up": {
|
||||
await config.callbacks.say("browser_action_result", JSON.stringify(browserActionResult))
|
||||
const result = formatResponse.toolResult(
|
||||
`The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${
|
||||
@@ -184,12 +184,14 @@ export class BrowserToolHandler implements IFullyManagedTool {
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
case "close":
|
||||
case "close": {
|
||||
const closeResult = formatResponse.toolResult(
|
||||
`The browser has been closed. You may now proceed to using other tools.`,
|
||||
)
|
||||
return closeResult
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
await config.services.browserSession.closeBrowser() // if any error occurs, the browser session is terminated
|
||||
|
||||
@@ -12,8 +12,6 @@ import type { StronglyTypedUIHelpers } from "../types/UIHelpers"
|
||||
export class CondenseHandler implements IToolHandler, IPartialBlockHandler {
|
||||
readonly name = "condense"
|
||||
|
||||
constructor() {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name}]`
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { telemetryService } from "@/services/telemetry"
|
||||
import type { ToolResponse } from "../../index"
|
||||
import { showNotificationForApprovalIfAutoApprovalEnabled } from "../../utils"
|
||||
import type { IFullyManagedTool } from "../ToolExecutorCoordinator"
|
||||
import type { ToolValidator } from "../ToolValidator"
|
||||
import type { TaskConfig } from "../types/TaskConfig"
|
||||
import type { StronglyTypedUIHelpers } from "../types/UIHelpers"
|
||||
import { ToolResultUtils } from "../utils/ToolResultUtils"
|
||||
@@ -16,8 +15,6 @@ import { ToolResultUtils } from "../utils/ToolResultUtils"
|
||||
export class ExecuteCommandToolHandler implements IFullyManagedTool {
|
||||
readonly name = "execute_command"
|
||||
|
||||
constructor(_validator: ToolValidator) {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name} for '${block.params.command}']`
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import type { StronglyTypedUIHelpers } from "../types/UIHelpers"
|
||||
export class LoadMcpDocumentationHandler implements IToolHandler, IPartialBlockHandler {
|
||||
readonly name = "load_mcp_documentation"
|
||||
|
||||
constructor() {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name}]`
|
||||
}
|
||||
@@ -19,7 +17,7 @@ export class LoadMcpDocumentationHandler implements IToolHandler, IPartialBlockH
|
||||
await uiHelpers.say("load_mcp_documentation", "", undefined, undefined, true)
|
||||
}
|
||||
|
||||
async execute(config: TaskConfig, block: ToolUse): Promise<ToolResponse> {
|
||||
async execute(config: TaskConfig, _block: ToolUse): Promise<ToolResponse> {
|
||||
// Show loading message at start of execution (self-managed now)
|
||||
await config.callbacks.say("load_mcp_documentation", "", undefined, undefined, false)
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ import type { StronglyTypedUIHelpers } from "../types/UIHelpers"
|
||||
export class NewTaskHandler implements IToolHandler, IPartialBlockHandler {
|
||||
readonly name = "new_task"
|
||||
|
||||
constructor() {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name} for creating a new task]`
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import type { StronglyTypedUIHelpers } from "../types/UIHelpers"
|
||||
export class PlanModeRespondHandler implements IToolHandler, IPartialBlockHandler {
|
||||
readonly name = "plan_mode_respond"
|
||||
|
||||
constructor() {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name}]`
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@ import type { StronglyTypedUIHelpers } from "../types/UIHelpers"
|
||||
export class ReportBugHandler implements IToolHandler, IPartialBlockHandler {
|
||||
readonly name = "report_bug"
|
||||
|
||||
constructor() {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name}]`
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import type { StronglyTypedUIHelpers } from "../types/UIHelpers"
|
||||
export class SummarizeTaskHandler implements IToolHandler, IPartialBlockHandler {
|
||||
readonly name = "summarize_task"
|
||||
|
||||
constructor() {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name}]`
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import { ToolResultUtils } from "../utils/ToolResultUtils"
|
||||
export class UseMcpToolHandler implements IFullyManagedTool {
|
||||
readonly name = "use_mcp_tool"
|
||||
|
||||
constructor() {}
|
||||
|
||||
getDescription(block: ToolUse): string {
|
||||
return `[${block.name} for '${block.params.server_name}']`
|
||||
}
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ export class ExternalDiffViewProvider extends DiffViewProvider {
|
||||
})
|
||||
}
|
||||
|
||||
protected async saveDocument(): Promise<Boolean> {
|
||||
protected async saveDocument(): Promise<boolean> {
|
||||
if (!this.activeDiffEditorId) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import * as vscode from "vscode"
|
||||
import { URI } from "vscode-uri"
|
||||
import { WebviewProvider } from "@/core/webview"
|
||||
import { WebviewProviderType } from "@/shared/webview/types"
|
||||
|
||||
export class ExternalWebviewProvider extends WebviewProvider {
|
||||
// This hostname cannot be changed without updating the external webview handler.
|
||||
private RESOURCE_HOSTNAME: string = "internal.resources"
|
||||
|
||||
constructor(context: vscode.ExtensionContext, providerType: WebviewProviderType) {
|
||||
super(context, providerType)
|
||||
}
|
||||
|
||||
override getWebviewUri(uri: URI) {
|
||||
if (uri.scheme !== "file") {
|
||||
return uri
|
||||
|
||||
@@ -159,7 +159,7 @@ export class VscodeDiffViewProvider extends DiffViewProvider {
|
||||
return this.activeDiffEditor.document.getText()
|
||||
}
|
||||
|
||||
protected override async saveDocument(): Promise<Boolean> {
|
||||
protected override async saveDocument(): Promise<boolean> {
|
||||
if (!this.activeDiffEditor) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { handleGrpcRequest, handleGrpcRequestCancel } from "@/core/controller/gr
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import type { ExtensionMessage } from "@/shared/ExtensionMessage"
|
||||
import { WebviewMessage } from "@/shared/WebviewMessage"
|
||||
import type { WebviewProviderType } from "@/shared/webview/types"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -22,10 +21,6 @@ export class VscodeWebviewProvider extends WebviewProvider implements vscode.Web
|
||||
private webview?: vscode.WebviewView | vscode.WebviewPanel
|
||||
private disposables: vscode.Disposable[] = []
|
||||
|
||||
constructor(context: vscode.ExtensionContext, providerType: WebviewProviderType) {
|
||||
super(context, providerType)
|
||||
}
|
||||
|
||||
override getWebviewUri(uri: Uri) {
|
||||
if (!this.webview) {
|
||||
throw new Error("Webview not initialized")
|
||||
@@ -128,7 +123,7 @@ export class VscodeWebviewProvider extends WebviewProvider implements vscode.Web
|
||||
// Listen for configuration changes
|
||||
vscode.workspace.onDidChangeConfiguration(
|
||||
async (e) => {
|
||||
if (e && e.affectsConfiguration("cline.mcpMarketplace.enabled")) {
|
||||
if (e?.affectsConfiguration("cline.mcpMarketplace.enabled")) {
|
||||
// Update state when marketplace tab setting changes
|
||||
await this.controller.postStateToWebview()
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@ const requestRegistry = new GrpcRequestRegistry()
|
||||
* Handles gRPC requests for the host bridge.
|
||||
*/
|
||||
export class GrpcHandler {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Handle a gRPC request for the host bridge.
|
||||
* @param service The service name
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SaveOpenDocumentIfDirtyRequest, SaveOpenDocumentIfDirtyResponse } from
|
||||
|
||||
export async function saveOpenDocumentIfDirty(request: SaveOpenDocumentIfDirtyRequest): Promise<SaveOpenDocumentIfDirtyResponse> {
|
||||
const existingDocument = vscode.workspace.textDocuments.find((doc) => arePathsEqual(doc.uri.fsPath, request.filePath))
|
||||
if (existingDocument && existingDocument.isDirty) {
|
||||
if (existingDocument?.isDirty) {
|
||||
await existingDocument.save()
|
||||
return { wasSaved: true }
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export async function* runClaudeCode(options: ClaudeCodeOptions): AsyncGenerator
|
||||
|
||||
// We rely on the assistant message. If the output was truncated, it's better having a poorly formatted message
|
||||
// from which to extract something, than throwing an error/showing the model didn't return any messages.
|
||||
if (processState.partialData && processState.partialData.startsWith(`{"type":"assistant"`)) {
|
||||
if (processState.partialData?.startsWith(`{"type":"assistant"`)) {
|
||||
yield processState.partialData
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ export abstract class DiffViewProvider {
|
||||
private streamedLines: string[] = []
|
||||
private newContent?: string
|
||||
|
||||
constructor() {}
|
||||
|
||||
public async open(relPath: string): Promise<void> {
|
||||
this.isEditing = true
|
||||
this.relPath = relPath
|
||||
@@ -138,7 +136,7 @@ export abstract class DiffViewProvider {
|
||||
*
|
||||
* @returns true if the file was saved.
|
||||
*/
|
||||
protected abstract saveDocument(): Promise<Boolean>
|
||||
protected abstract saveDocument(): Promise<boolean>
|
||||
|
||||
/**
|
||||
* Closes all open diff views.
|
||||
|
||||
@@ -61,7 +61,7 @@ export function formatContentBlockToMarkdown(block: Anthropic.ContentBlockParam)
|
||||
return `[Image]`
|
||||
case "document":
|
||||
return `[Document]`
|
||||
case "tool_use":
|
||||
case "tool_use": {
|
||||
let input: string
|
||||
if (typeof block.input === "object" && block.input !== null) {
|
||||
input = Object.entries(block.input)
|
||||
@@ -71,6 +71,7 @@ export function formatContentBlockToMarkdown(block: Anthropic.ContentBlockParam)
|
||||
input = String(block.input)
|
||||
}
|
||||
return `[Tool Use: ${block.name}]\n${input}`
|
||||
}
|
||||
case "tool_result":
|
||||
if (typeof block.content === "string") {
|
||||
return `[Tool${block.is_error ? " (Error)" : ""}]\n${block.content}`
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function callTextExtractionFunctions(filePath: string): Promise<str
|
||||
return extractTextFromIPYNB(filePath)
|
||||
case ".xlsx":
|
||||
return extractTextFromExcel(filePath)
|
||||
default:
|
||||
default: {
|
||||
const fileBuffer = await fs.readFile(filePath)
|
||||
if (fileBuffer.byteLength > 20 * 1000 * 1024) {
|
||||
// 20MB limit (20 * 1000 * 1024 bytes, decimal MB)
|
||||
@@ -58,6 +58,7 @@ export async function callTextExtractionFunctions(filePath: string): Promise<str
|
||||
}
|
||||
const encoding = await detectEncoding(fileBuffer, fileExtension)
|
||||
return iconv.decode(fileBuffer, encoding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ export async function detectImageUrl(url: string): Promise<boolean> {
|
||||
})
|
||||
|
||||
const contentType = response.headers["content-type"]
|
||||
return contentType && contentType.startsWith("image/")
|
||||
return contentType?.startsWith("image/")
|
||||
} catch (_error) {
|
||||
// If we can't determine, fall back to checking the file extension
|
||||
return /\.(jpg|jpeg|png|gif|webp|bmp|svg|tiff|tif|avif)$/i.test(url)
|
||||
|
||||
@@ -117,7 +117,7 @@ export class TerminalManager {
|
||||
try {
|
||||
const stateChangeDisposable = vscode.window.onDidChangeTerminalState((terminal) => {
|
||||
const terminalInfo = this.findTerminalInfoByTerminal(terminal)
|
||||
if (terminalInfo && terminalInfo.pendingCwdChange && terminalInfo.cwdResolved) {
|
||||
if (terminalInfo?.pendingCwdChange && terminalInfo.cwdResolved) {
|
||||
// Check if CWD has been updated to match the expected path
|
||||
if (this.isCwdMatchingExpected(terminalInfo)) {
|
||||
const resolver = terminalInfo.cwdResolved.resolve
|
||||
@@ -213,7 +213,7 @@ export class TerminalManager {
|
||||
.finally(() => {
|
||||
console.log(`[TerminalManager Test] Proceeding with command execution for terminal ${terminalInfo.id}.`)
|
||||
const existingProcess = this.processes.get(terminalInfo.id)
|
||||
if (existingProcess && existingProcess.waitForShellIntegration) {
|
||||
if (existingProcess?.waitForShellIntegration) {
|
||||
existingProcess.waitForShellIntegration = false
|
||||
existingProcess.run(terminalInfo.terminal, command)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
||||
const returnCurrentTerminalContents = async () => {
|
||||
try {
|
||||
const terminalSnapshot = await getLatestTerminalOutput()
|
||||
if (terminalSnapshot && terminalSnapshot.trim()) {
|
||||
if (terminalSnapshot?.trim()) {
|
||||
const fallbackMessage = `The command's output could not be captured due to some technical issue, however it has been executed successfully. Here's the current terminal's content to help you get the command's output:\n\n${terminalSnapshot}`
|
||||
this.emit("line", fallbackMessage)
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
if (terminal.shellIntegration && terminal.shellIntegration.executeCommand) {
|
||||
if (terminal.shellIntegration?.executeCommand) {
|
||||
const execution = terminal.shellIntegration.executeCommand(command)
|
||||
const stream = execution.read()
|
||||
// todo: need to handle errors
|
||||
|
||||
@@ -26,7 +26,7 @@ export class FeatureFlagsProviderFactory {
|
||||
*/
|
||||
public static createProvider(config: FeatureFlagsProviderConfig): IFeatureFlagsProvider {
|
||||
switch (config.type) {
|
||||
case "posthog":
|
||||
case "posthog": {
|
||||
// Get the shared PostHog client from PostHogClientProvider
|
||||
const client = PostHogClientProvider.getClient()
|
||||
if (client) {
|
||||
@@ -34,6 +34,7 @@ export class FeatureFlagsProviderFactory {
|
||||
}
|
||||
// Fall back to NoOp provider if no client is available
|
||||
return new NoOpFeatureFlagsProvider()
|
||||
}
|
||||
case "none":
|
||||
return new NoOpFeatureFlagsProvider()
|
||||
default:
|
||||
|
||||
@@ -924,7 +924,7 @@ export class McpHub {
|
||||
|
||||
// Update the tools list to reflect the change
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
if (connection && connection.server.tools) {
|
||||
if (connection?.server.tools) {
|
||||
// Update the autoApprove property of each tool in the in-memory server object
|
||||
connection.server.tools = connection.server.tools.map((tool) => ({
|
||||
...tool,
|
||||
@@ -969,7 +969,7 @@ export class McpHub {
|
||||
|
||||
// Update the tools list to reflect the change
|
||||
const connection = this.connections.find((conn) => conn.server.name === serverName)
|
||||
if (connection && connection.server.tools) {
|
||||
if (connection?.server.tools) {
|
||||
// Update the autoApprove property of each tool in the in-memory server object
|
||||
connection.server.tools = connection.server.tools.map((tool) => ({
|
||||
...tool,
|
||||
|
||||
@@ -322,7 +322,7 @@ export function createTestServer(controller: Controller): http.Server {
|
||||
}
|
||||
|
||||
// Get file changes
|
||||
let fileChanges
|
||||
let fileChanges: { created: string[]; modified: string[]; deleted: string[] }
|
||||
try {
|
||||
// Get the workspace path using our helper function
|
||||
const workspacePath = await getCwd()
|
||||
|
||||
@@ -215,7 +215,7 @@ describe("Mention Regex", () => {
|
||||
cases.forEach(([input, expected]) => {
|
||||
const match = mentionRegex.exec(input)
|
||||
const actual = match ? match[0] : null
|
||||
if (expected && expected.includes("41 chars")) {
|
||||
if (expected?.includes("41 chars")) {
|
||||
// Special case: should match first 40 chars
|
||||
expect(actual).to.equal("@abcdef1234567890abcdef1234567890abcdef12")
|
||||
} else {
|
||||
|
||||
@@ -86,7 +86,6 @@ function convertProtoStatusToMcp(status: McpServerStatus): McpServer["status"] {
|
||||
return "connected"
|
||||
case McpServerStatus.MCP_SERVER_STATUS_CONNECTING:
|
||||
return "connecting"
|
||||
case McpServerStatus.MCP_SERVER_STATUS_DISCONNECTED:
|
||||
default: // Includes UNSPECIFIED if it were present, maps to disconnected
|
||||
return "disconnected"
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ async function asyncIteratorToCallbacks<T>(stream: AsyncIterable<T>, callbacks:
|
||||
try {
|
||||
// Process each item in the stream
|
||||
for await (const response of stream) {
|
||||
callbacks.onResponse && callbacks.onResponse(response)
|
||||
callbacks.onResponse?.(response)
|
||||
}
|
||||
// Stream completed successfully
|
||||
callbacks.onComplete && callbacks.onComplete()
|
||||
callbacks.onComplete?.()
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err : new Error(String(err))
|
||||
if (callbacks.onError) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { ClineApiServerMock } from "../fixtures/server"
|
||||
interface E2ETestDirectories {
|
||||
workspaceDir: string
|
||||
userDataDir: string
|
||||
extensionsDir: string
|
||||
}
|
||||
|
||||
export class E2ETestHelper {
|
||||
@@ -184,6 +183,7 @@ export class E2ETestHelper {
|
||||
*/
|
||||
export const e2e = test
|
||||
.extend<{ server: ClineApiServerMock | null }>({
|
||||
// biome-ignore lint/correctness/noEmptyPattern: no dependency
|
||||
server: async ({}, use) => {
|
||||
console.log("=== SERVER FIXTURE CALLED ===")
|
||||
// Start server if it doesn't exist
|
||||
@@ -198,18 +198,17 @@ export const e2e = test
|
||||
},
|
||||
})
|
||||
.extend<E2ETestDirectories>({
|
||||
// biome-ignore lint/correctness/noEmptyPattern: no dependency
|
||||
workspaceDir: async ({}, use) => {
|
||||
await use(path.join(E2ETestHelper.E2E_TESTS_DIR, "fixtures", "workspace"))
|
||||
},
|
||||
// biome-ignore lint/correctness/noEmptyPattern: no dependency
|
||||
userDataDir: async ({}, use) => {
|
||||
await use(mkdtempSync(path.join(os.tmpdir(), "vsce")))
|
||||
},
|
||||
extensionsDir: async ({}, use) => {
|
||||
await use(mkdtempSync(path.join(os.tmpdir(), "vsce")))
|
||||
},
|
||||
})
|
||||
.extend<{ openVSCode: () => Promise<ElectronApplication> }>({
|
||||
openVSCode: async ({ workspaceDir, userDataDir, extensionsDir }, use, testInfo) => {
|
||||
openVSCode: async ({ workspaceDir, userDataDir }, use, testInfo) => {
|
||||
const executablePath = await downloadAndUnzipVSCode("stable", undefined, new SilentReporter())
|
||||
|
||||
await use(async () => {
|
||||
@@ -233,7 +232,7 @@ export const e2e = test
|
||||
"--skip-welcome",
|
||||
"--skip-release-notes",
|
||||
`--user-data-dir=${userDataDir}`,
|
||||
`--extensions-dir=${extensionsDir}`,
|
||||
`--extensions-dir=${userDataDir}`,
|
||||
`--install-extension=${path.join(E2ETestHelper.CODEBASE_ROOT_DIR, "dist", "e2e.vsix")}`,
|
||||
`--extensionDevelopmentPath=${E2ETestHelper.CODEBASE_ROOT_DIR}`,
|
||||
workspaceDir,
|
||||
@@ -245,7 +244,7 @@ export const e2e = test
|
||||
},
|
||||
})
|
||||
.extend<{ app: ElectronApplication }>({
|
||||
app: async ({ openVSCode, userDataDir, extensionsDir }, use) => {
|
||||
app: async ({ openVSCode, userDataDir }, use) => {
|
||||
const app = await openVSCode()
|
||||
|
||||
try {
|
||||
@@ -253,14 +252,12 @@ export const e2e = test
|
||||
} finally {
|
||||
await app.close()
|
||||
// Cleanup in parallel
|
||||
await Promise.allSettled([
|
||||
E2ETestHelper.rmForRetries(userDataDir, { recursive: true }),
|
||||
E2ETestHelper.rmForRetries(extensionsDir, { recursive: true }),
|
||||
])
|
||||
await Promise.allSettled([E2ETestHelper.rmForRetries(userDataDir, { recursive: true })])
|
||||
}
|
||||
},
|
||||
})
|
||||
.extend<{ helper: E2ETestHelper }>({
|
||||
// biome-ignore lint/correctness/noEmptyPattern: no dependency
|
||||
helper: async ({}, use) => {
|
||||
const helper = new E2ETestHelper()
|
||||
await use(helper)
|
||||
|
||||
@@ -449,7 +449,7 @@ class StandaloneTerminalManager {
|
||||
disposeAll() {
|
||||
// Terminate all processes
|
||||
for (const [_terminalId, process] of this.processes) {
|
||||
if (process && process.terminate) {
|
||||
if (process?.terminate) {
|
||||
process.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,7 +552,7 @@ const BrowserSessionRowContent = memo(
|
||||
</div>
|
||||
)
|
||||
|
||||
case "browser_action":
|
||||
case "browser_action": {
|
||||
const browserAction = JSON.parse(message.text || "{}") as ClineSayBrowserAction
|
||||
return (
|
||||
<BrowserActionBox
|
||||
@@ -561,6 +561,7 @@ const BrowserSessionRowContent = memo(
|
||||
text={browserAction.text}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
return null
|
||||
|
||||
@@ -307,7 +307,7 @@ export const ChatRowContent = memo(
|
||||
),
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>Cline wants to execute this command:</span>,
|
||||
]
|
||||
case "use_mcp_server":
|
||||
case "use_mcp_server": {
|
||||
const mcpServerUse = JSON.parse(message.text || "{}") as ClineAskUseMcpServer
|
||||
return [
|
||||
isMcpServerResponding ? (
|
||||
@@ -330,6 +330,7 @@ export const ChatRowContent = memo(
|
||||
MCP server:
|
||||
</span>,
|
||||
]
|
||||
}
|
||||
case "completion_result":
|
||||
return [
|
||||
<span
|
||||
@@ -444,7 +445,7 @@ export const ChatRowContent = memo(
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "readFile":
|
||||
case "readFile": {
|
||||
const isImage = isImageFile(tool.path || "")
|
||||
return (
|
||||
<>
|
||||
@@ -511,6 +512,7 @@ export const ChatRowContent = memo(
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
case "listFilesTopLevel":
|
||||
return (
|
||||
<>
|
||||
@@ -1072,7 +1074,7 @@ export const ChatRowContent = memo(
|
||||
text={message.text}
|
||||
/>
|
||||
)
|
||||
case "user_feedback_diff":
|
||||
case "user_feedback_diff": {
|
||||
const tool = JSON.parse(message.text || "{}") as ClineSayTool
|
||||
return (
|
||||
<div
|
||||
@@ -1088,6 +1090,7 @@ export const ChatRowContent = memo(
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
case "error":
|
||||
return <ErrorRow errorType="error" message={message} />
|
||||
case "diff_error":
|
||||
@@ -1111,7 +1114,7 @@ export const ChatRowContent = memo(
|
||||
Loading MCP documentation
|
||||
</div>
|
||||
)
|
||||
case "completion_result":
|
||||
case "completion_result": {
|
||||
const hasChanges = message.text?.endsWith(COMPLETION_RESULT_CHANGES_FLAG) ?? false
|
||||
const text = hasChanges ? message.text?.slice(0, -COMPLETION_RESULT_CHANGES_FLAG.length) : message.text
|
||||
return (
|
||||
@@ -1178,6 +1181,7 @@ export const ChatRowContent = memo(
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
case "shell_integration_warning":
|
||||
return (
|
||||
<div
|
||||
@@ -1323,7 +1327,7 @@ export const ChatRowContent = memo(
|
||||
} else {
|
||||
return null // Don't render anything when we get a completion_result ask without text
|
||||
}
|
||||
case "followup":
|
||||
case "followup": {
|
||||
let question: string | undefined
|
||||
let options: string[] | undefined
|
||||
let selected: string | undefined
|
||||
@@ -1373,6 +1377,7 @@ export const ChatRowContent = memo(
|
||||
</WithCopyButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
case "new_task":
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -420,7 +420,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
setSearchLoading(true)
|
||||
|
||||
// Map ContextMenuOptionType to FileSearchType enum
|
||||
let searchType
|
||||
let searchType: FileSearchType | undefined
|
||||
if (type === ContextMenuOptionType.File) {
|
||||
searchType = FileSearchType.FILE
|
||||
} else if (type === ContextMenuOptionType.Folder) {
|
||||
@@ -1156,8 +1156,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
return `${selectedProvider}:${liteLlmModelId}`
|
||||
case "requesty":
|
||||
return `${selectedProvider}:${requestyModelId}`
|
||||
case "anthropic":
|
||||
case "openrouter":
|
||||
default:
|
||||
return `${selectedProvider}:${selectedModelId}`
|
||||
}
|
||||
|
||||
@@ -205,12 +205,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
value: selectedModelInfo.supportsImages,
|
||||
}),
|
||||
)
|
||||
if (
|
||||
response &&
|
||||
response.values1 &&
|
||||
response.values2 &&
|
||||
(response.values1.length > 0 || response.values2.length > 0)
|
||||
) {
|
||||
if (response?.values1 && response.values2 && (response.values1.length > 0 || response.values2.length > 0)) {
|
||||
const currentTotal = selectedImages.length + selectedFiles.length
|
||||
const availableSlots = MAX_IMAGES_AND_FILES_PER_MESSAGE - currentTotal
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ const AutoApproveModal: React.FC<AutoApproveModalProps> = ({
|
||||
|
||||
useClickAway(modalRef, (e) => {
|
||||
// Skip if click was on the button that toggles the modal
|
||||
if (buttonRef.current && buttonRef.current.contains(e.target as Node)) {
|
||||
if (buttonRef.current?.contains(e.target as Node)) {
|
||||
return
|
||||
}
|
||||
setIsVisible(false)
|
||||
|
||||
@@ -40,7 +40,6 @@ const ButtonContainer = styled.div<{ $position?: "top-right" | "bottom-right" }>
|
||||
switch (props.$position) {
|
||||
case "bottom-right":
|
||||
return "bottom: 2px; right: 2px;"
|
||||
case "top-right":
|
||||
default:
|
||||
return "top: 5px; right: 5px;"
|
||||
}
|
||||
|
||||
@@ -59,13 +59,17 @@ const remarkUrlToLink = () => {
|
||||
visit(tree, "text", (node: any, index, parent) => {
|
||||
const urlRegex = /https?:\/\/[^\s<>)"]+/g
|
||||
const matches = node.value.match(urlRegex)
|
||||
if (!matches) return
|
||||
if (!matches) {
|
||||
return
|
||||
}
|
||||
|
||||
const parts = node.value.split(urlRegex)
|
||||
const children: any[] = []
|
||||
|
||||
parts.forEach((part: string, i: number) => {
|
||||
if (part) children.push({ type: "text", value: part })
|
||||
if (part) {
|
||||
children.push({ type: "text", value: part })
|
||||
}
|
||||
if (matches[i]) {
|
||||
children.push({
|
||||
type: "link",
|
||||
@@ -96,19 +100,25 @@ const remarkHighlightActMode = () => {
|
||||
// Added negative lookahead to avoid matching if already followed by the shortcut
|
||||
const actModeRegex = /\bto\s+Act\s+Mode\b(?!\s*\(⌘⇧A\))/i
|
||||
|
||||
if (!node.value.match(actModeRegex)) return
|
||||
if (!node.value.match(actModeRegex)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Split the text by the matches
|
||||
const parts = node.value.split(actModeRegex)
|
||||
const matches = node.value.match(actModeRegex)
|
||||
|
||||
if (!matches || parts.length <= 1) return
|
||||
if (!matches || parts.length <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
const children: any[] = []
|
||||
|
||||
parts.forEach((part: string, i: number) => {
|
||||
// Add the text before the match
|
||||
if (part) children.push({ type: "text", value: part })
|
||||
if (part) {
|
||||
children.push({ type: "text", value: part })
|
||||
}
|
||||
|
||||
// Add the match, but only make "Act Mode" bold (not the "to" part)
|
||||
if (matches[i]) {
|
||||
@@ -156,22 +166,32 @@ const remarkPreventBoldFilenames = () => {
|
||||
return (tree: any) => {
|
||||
visit(tree, "strong", (node: any, index: number | undefined, parent: any) => {
|
||||
// Only process if there's a next node (potential file extension)
|
||||
if (!parent || typeof index === "undefined" || index === parent.children.length - 1) return
|
||||
if (!parent || typeof index === "undefined" || index === parent.children.length - 1) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextNode = parent.children[index + 1]
|
||||
|
||||
// Check if next node is text and starts with . followed by extension
|
||||
if (nextNode.type !== "text" || !nextNode.value.match(/^\.[a-zA-Z0-9]+/)) return
|
||||
if (nextNode.type !== "text" || !nextNode.value.match(/^\.[a-zA-Z0-9]+/)) {
|
||||
return
|
||||
}
|
||||
|
||||
// If the strong node has multiple children, something weird is happening
|
||||
if (node.children?.length !== 1) return
|
||||
if (node.children?.length !== 1) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the text content from inside the strong node
|
||||
const strongContent = node.children?.[0]?.value
|
||||
if (!strongContent || typeof strongContent !== "string") return
|
||||
if (!strongContent || typeof strongContent !== "string") {
|
||||
return
|
||||
}
|
||||
|
||||
// Validate that the strong content is a valid filename
|
||||
if (!strongContent.match(/^[a-zA-Z0-9_-]+$/)) return
|
||||
if (!strongContent.match(/^[a-zA-Z0-9_-]+$/)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Combine into a single text node
|
||||
const newNode = {
|
||||
@@ -283,7 +303,9 @@ const PreWithCopyButton = ({ children, ...preProps }: React.HTMLAttributes<HTMLP
|
||||
const codeElement = preRef.current.querySelector("code")
|
||||
const textToCopy = codeElement ? codeElement.textContent : preRef.current.textContent
|
||||
|
||||
if (!textToCopy) return
|
||||
if (!textToCopy) {
|
||||
return
|
||||
}
|
||||
return textToCopy
|
||||
}
|
||||
return null
|
||||
@@ -305,7 +327,7 @@ const PreWithCopyButton = ({ children, ...preProps }: React.HTMLAttributes<HTMLP
|
||||
const remarkFilePathDetection = () => {
|
||||
return async (tree: Node) => {
|
||||
const fileNameRegex = /^(?!\/)[\w\-./]+(?<!\/)$/
|
||||
const inlineCodeNodes: any[] = []
|
||||
const _inlineCodeNodes: any[] = []
|
||||
const filePathPromises: Promise<void>[] = []
|
||||
|
||||
// Collect all inline code nodes that might be file paths
|
||||
@@ -399,9 +421,12 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
||||
// Handle both string children and array of children cases
|
||||
const childrenText = React.Children.toArray(props.children)
|
||||
.map((child) => {
|
||||
if (typeof child === "string") return child
|
||||
if (typeof child === "object" && "props" in child && child.props.children)
|
||||
if (typeof child === "string") {
|
||||
return child
|
||||
}
|
||||
if (typeof child === "object" && "props" in child && child.props.children) {
|
||||
return String(child.props.children)
|
||||
}
|
||||
return ""
|
||||
})
|
||||
.join("")
|
||||
|
||||
@@ -248,7 +248,6 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
||||
case "mostRelevant":
|
||||
// NOTE: you must never sort directly on object since it will cause members to be reordered
|
||||
return searchQuery ? 0 : b.ts - a.ts // Keep fuse order if searching, otherwise sort by newest
|
||||
case "newest":
|
||||
default:
|
||||
return b.ts - a.ts
|
||||
}
|
||||
@@ -825,7 +824,7 @@ export const highlight = (fuseSearchResult: FuseResult<any>[], highlightClassNam
|
||||
}
|
||||
|
||||
return fuseSearchResult
|
||||
.filter(({ matches }) => matches && matches.length)
|
||||
.filter(({ matches }) => matches?.length)
|
||||
.map(({ item, matches }) => {
|
||||
const highlightedItem = { ...item }
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
value: apiConfiguration?.ollamaBaseUrl || "",
|
||||
}),
|
||||
)
|
||||
if (response && response.values) {
|
||||
if (response?.values) {
|
||||
setOllamaModels(response.values)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -192,7 +192,7 @@ const BasetenModelPicker: React.FC<BasetenModelPickerProps> = ({ isPopup, curren
|
||||
}
|
||||
|
||||
const hasInfo = useMemo(() => {
|
||||
return selectedModelInfo && selectedModelInfo.description
|
||||
return selectedModelInfo?.description
|
||||
}, [selectedModelInfo])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -18,11 +18,11 @@ export const DifyProvider = ({ showModelOptions, isPopup, currentMode }: DifyPro
|
||||
const { handleFieldChange } = useApiConfigurationHandlers()
|
||||
|
||||
// Use debounced input for proper state management
|
||||
const [baseUrlValue, setBaseUrlValue] = useDebouncedInput(apiConfiguration?.difyBaseUrl || "", (value) =>
|
||||
const [_baseUrlValue, _setBaseUrlValue] = useDebouncedInput(apiConfiguration?.difyBaseUrl || "", (value) =>
|
||||
handleFieldChange("difyBaseUrl", value),
|
||||
)
|
||||
|
||||
const [apiKeyValue, setApiKeyValue] = useDebouncedInput(apiConfiguration?.difyApiKey || "", (value) =>
|
||||
const [_apiKeyValue, _setApiKeyValue] = useDebouncedInput(apiConfiguration?.difyApiKey || "", (value) =>
|
||||
handleFieldChange("difyApiKey", value),
|
||||
)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export const OllamaProvider = ({ showModelOptions, isPopup, currentMode }: Ollam
|
||||
value: apiConfiguration?.ollamaBaseUrl || "",
|
||||
}),
|
||||
)
|
||||
if (response && response.values) {
|
||||
if (response?.values) {
|
||||
setOllamaModels(response.values)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export const VSCodeLmProvider = ({ currentMode }: VSCodeLmProviderProps) => {
|
||||
const requestVsCodeLmModels = useCallback(async () => {
|
||||
try {
|
||||
const response = await ModelsServiceClient.getVsCodeLmModels(EmptyRequest.create({}))
|
||||
if (response && response.models) {
|
||||
if (response?.models) {
|
||||
setVsCodeLmModels(response.models)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -36,7 +36,7 @@ export const VercelAIGatewayProvider = ({ showModelOptions, isPopup, currentMode
|
||||
setIsLoadingModels(true)
|
||||
ModelsServiceClient.refreshVercelAiGatewayModels(EmptyRequest.create({}))
|
||||
.then((response) => {
|
||||
if (response && response.models) {
|
||||
if (response?.models) {
|
||||
setVercelAiGatewayModels(response.models)
|
||||
}
|
||||
setIsLoadingModels(false)
|
||||
|
||||
@@ -68,7 +68,7 @@ export const XaiProvider = ({ showModelOptions, isPopup, currentMode }: XaiProvi
|
||||
selectedModelId={selectedModelId}
|
||||
/>
|
||||
|
||||
{selectedModelId && selectedModelId.includes("3-mini") && (
|
||||
{selectedModelId?.includes("3-mini") && (
|
||||
<>
|
||||
<VSCodeCheckbox
|
||||
checked={reasoningEffortSelected}
|
||||
|
||||
@@ -107,7 +107,7 @@ export function normalizeApiConfiguration(
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
case "claude-code":
|
||||
return getProviderData(claudeCodeModels, claudeCodeDefaultModelId)
|
||||
case "bedrock":
|
||||
case "bedrock": {
|
||||
const awsBedrockCustomSelected =
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeAwsBedrockCustomSelected
|
||||
@@ -124,6 +124,7 @@ export function normalizeApiConfiguration(
|
||||
}
|
||||
}
|
||||
return getProviderData(bedrockModels, bedrockDefaultModelId)
|
||||
}
|
||||
case "vertex":
|
||||
return getProviderData(vertexModels, vertexDefaultModelId)
|
||||
case "gemini":
|
||||
@@ -132,11 +133,12 @@ export function normalizeApiConfiguration(
|
||||
return getProviderData(openAiNativeModels, openAiNativeDefaultModelId)
|
||||
case "deepseek":
|
||||
return getProviderData(deepSeekModels, deepSeekDefaultModelId)
|
||||
case "qwen":
|
||||
case "qwen": {
|
||||
const qwenModels = apiConfiguration?.qwenApiLine === "china" ? mainlandQwenModels : internationalQwenModels
|
||||
const qwenDefaultId =
|
||||
apiConfiguration?.qwenApiLine === "china" ? mainlandQwenDefaultModelId : internationalQwenDefaultModelId
|
||||
return getProviderData(qwenModels, qwenDefaultId)
|
||||
}
|
||||
case "qwen-code":
|
||||
return getProviderData(qwenCodeModels, qwenCodeDefaultModelId)
|
||||
case "doubao":
|
||||
@@ -145,7 +147,7 @@ export function normalizeApiConfiguration(
|
||||
return getProviderData(mistralModels, mistralDefaultModelId)
|
||||
case "asksage":
|
||||
return getProviderData(askSageModels, askSageDefaultModelId)
|
||||
case "openrouter":
|
||||
case "openrouter": {
|
||||
const openRouterModelId =
|
||||
currentMode === "plan" ? apiConfiguration?.planModeOpenRouterModelId : apiConfiguration?.actModeOpenRouterModelId
|
||||
const openRouterModelInfo =
|
||||
@@ -157,7 +159,8 @@ export function normalizeApiConfiguration(
|
||||
selectedModelId: openRouterModelId || openRouterDefaultModelId,
|
||||
selectedModelInfo: openRouterModelInfo || openRouterDefaultModelInfo,
|
||||
}
|
||||
case "requesty":
|
||||
}
|
||||
case "requesty": {
|
||||
const requestyModelId =
|
||||
currentMode === "plan" ? apiConfiguration?.planModeRequestyModelId : apiConfiguration?.actModeRequestyModelId
|
||||
const requestyModelInfo =
|
||||
@@ -167,7 +170,8 @@ export function normalizeApiConfiguration(
|
||||
selectedModelId: requestyModelId || requestyDefaultModelId,
|
||||
selectedModelInfo: requestyModelInfo || requestyDefaultModelInfo,
|
||||
}
|
||||
case "cline":
|
||||
}
|
||||
case "cline": {
|
||||
const clineOpenRouterModelId =
|
||||
(currentMode === "plan"
|
||||
? apiConfiguration?.planModeOpenRouterModelId
|
||||
@@ -181,7 +185,8 @@ export function normalizeApiConfiguration(
|
||||
selectedModelId: clineOpenRouterModelId,
|
||||
selectedModelInfo: clineOpenRouterModelInfo,
|
||||
}
|
||||
case "openai":
|
||||
}
|
||||
case "openai": {
|
||||
const openAiModelId =
|
||||
currentMode === "plan" ? apiConfiguration?.planModeOpenAiModelId : apiConfiguration?.actModeOpenAiModelId
|
||||
const openAiModelInfo =
|
||||
@@ -191,7 +196,8 @@ export function normalizeApiConfiguration(
|
||||
selectedModelId: openAiModelId || "",
|
||||
selectedModelInfo: openAiModelInfo || openAiModelInfoSaneDefaults,
|
||||
}
|
||||
case "ollama":
|
||||
}
|
||||
case "ollama": {
|
||||
const ollamaModelId =
|
||||
currentMode === "plan" ? apiConfiguration?.planModeOllamaModelId : apiConfiguration?.actModeOllamaModelId
|
||||
return {
|
||||
@@ -202,7 +208,8 @@ export function normalizeApiConfiguration(
|
||||
contextWindow: Number(apiConfiguration?.ollamaApiOptionsCtxNum ?? 32768),
|
||||
},
|
||||
}
|
||||
case "lmstudio":
|
||||
}
|
||||
case "lmstudio": {
|
||||
const lmStudioModelId =
|
||||
currentMode === "plan" ? apiConfiguration?.planModeLmStudioModelId : apiConfiguration?.actModeLmStudioModelId
|
||||
return {
|
||||
@@ -213,7 +220,8 @@ export function normalizeApiConfiguration(
|
||||
contextWindow: Number(apiConfiguration?.lmStudioMaxTokens ?? 32768),
|
||||
},
|
||||
}
|
||||
case "vscode-lm":
|
||||
}
|
||||
case "vscode-lm": {
|
||||
const vsCodeLmModelSelector =
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeVsCodeLmModelSelector
|
||||
@@ -226,7 +234,8 @@ export function normalizeApiConfiguration(
|
||||
supportsImages: false, // VSCode LM API currently doesn't support images
|
||||
},
|
||||
}
|
||||
case "litellm":
|
||||
}
|
||||
case "litellm": {
|
||||
const liteLlmModelId =
|
||||
currentMode === "plan" ? apiConfiguration?.planModeLiteLlmModelId : apiConfiguration?.actModeLiteLlmModelId
|
||||
const liteLlmModelInfo =
|
||||
@@ -236,11 +245,12 @@ export function normalizeApiConfiguration(
|
||||
selectedModelId: liteLlmModelId || "",
|
||||
selectedModelInfo: liteLlmModelInfo || liteLlmModelInfoSaneDefaults,
|
||||
}
|
||||
}
|
||||
case "xai":
|
||||
return getProviderData(xaiModels, xaiDefaultModelId)
|
||||
case "moonshot":
|
||||
return getProviderData(moonshotModels, moonshotDefaultModelId)
|
||||
case "huggingface":
|
||||
case "huggingface": {
|
||||
const huggingFaceModelId =
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeHuggingFaceModelId
|
||||
@@ -254,13 +264,14 @@ export function normalizeApiConfiguration(
|
||||
selectedModelId: huggingFaceModelId || huggingFaceDefaultModelId,
|
||||
selectedModelInfo: huggingFaceModelInfo || huggingFaceModels[huggingFaceDefaultModelId],
|
||||
}
|
||||
}
|
||||
case "nebius":
|
||||
return getProviderData(nebiusModels, nebiusDefaultModelId)
|
||||
case "sambanova":
|
||||
return getProviderData(sambanovaModels, sambanovaDefaultModelId)
|
||||
case "cerebras":
|
||||
return getProviderData(cerebrasModels, cerebrasDefaultModelId)
|
||||
case "groq":
|
||||
case "groq": {
|
||||
const groqModelId =
|
||||
currentMode === "plan" ? apiConfiguration?.planModeGroqModelId : apiConfiguration?.actModeGroqModelId
|
||||
const groqModelInfo =
|
||||
@@ -270,7 +281,8 @@ export function normalizeApiConfiguration(
|
||||
selectedModelId: groqModelId || groqDefaultModelId,
|
||||
selectedModelInfo: groqModelInfo || groqModels[groqDefaultModelId],
|
||||
}
|
||||
case "baseten":
|
||||
}
|
||||
case "baseten": {
|
||||
const basetenModelId =
|
||||
currentMode === "plan" ? apiConfiguration?.planModeBasetenModelId : apiConfiguration?.actModeBasetenModelId
|
||||
const basetenModelInfo =
|
||||
@@ -285,9 +297,10 @@ export function normalizeApiConfiguration(
|
||||
description: "Baseten model",
|
||||
},
|
||||
}
|
||||
}
|
||||
case "sapaicore":
|
||||
return getProviderData(sapAiCoreModels, sapAiCoreDefaultModelId)
|
||||
case "huawei-cloud-maas":
|
||||
case "huawei-cloud-maas": {
|
||||
const huaweiCloudMaasModelId =
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeHuaweiCloudMaasModelId
|
||||
@@ -301,6 +314,7 @@ export function normalizeApiConfiguration(
|
||||
selectedModelId: huaweiCloudMaasModelId || huaweiCloudMaasDefaultModelId,
|
||||
selectedModelInfo: huaweiCloudMaasModelInfo || huaweiCloudMaasModels[huaweiCloudMaasDefaultModelId],
|
||||
}
|
||||
}
|
||||
case "dify":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
@@ -315,7 +329,7 @@ export function normalizeApiConfiguration(
|
||||
description: "Dify workflow - model selection is configured in your Dify application",
|
||||
},
|
||||
}
|
||||
case "vercel-ai-gateway":
|
||||
case "vercel-ai-gateway": {
|
||||
const vercelAiGatewayModelId =
|
||||
currentMode === "plan"
|
||||
? apiConfiguration?.planModeVercelAiGatewayModelId
|
||||
@@ -329,12 +343,14 @@ export function normalizeApiConfiguration(
|
||||
selectedModelId: vercelAiGatewayModelId || vercelAiGatewayDefaultModelId,
|
||||
selectedModelInfo: vercelAiGatewayModelInfo || vercelAiGatewayDefaultModelInfo,
|
||||
}
|
||||
case "zai":
|
||||
}
|
||||
case "zai": {
|
||||
const zaiModels = apiConfiguration?.zaiApiLine === "china" ? mainlandZAiModels : internationalZAiModels
|
||||
const zaiDefaultId =
|
||||
apiConfiguration?.zaiApiLine === "china" ? mainlandZAiDefaultModelId : internationalZAiDefaultModelId
|
||||
return getProviderData(zaiModels, zaiDefaultId)
|
||||
case "fireworks":
|
||||
}
|
||||
case "fireworks": {
|
||||
const fireworksModelId =
|
||||
currentMode === "plan" ? apiConfiguration?.planModeFireworksModelId : apiConfiguration?.actModeFireworksModelId
|
||||
return {
|
||||
@@ -345,6 +361,7 @@ export function normalizeApiConfiguration(
|
||||
? fireworksModels[fireworksModelId as keyof typeof fireworksModels]
|
||||
: fireworksModels[fireworksDefaultModelId],
|
||||
}
|
||||
}
|
||||
default:
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
}
|
||||
@@ -601,24 +618,6 @@ export async function syncModeConfigurations(
|
||||
updates.planModeVercelAiGatewayModelInfo = sourceFields.vercelAiGatewayModelInfo
|
||||
updates.actModeVercelAiGatewayModelInfo = sourceFields.vercelAiGatewayModelInfo
|
||||
break
|
||||
|
||||
// Providers that use apiProvider + apiModelId fields
|
||||
case "anthropic":
|
||||
case "claude-code":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "openai-native":
|
||||
case "deepseek":
|
||||
case "qwen":
|
||||
case "doubao":
|
||||
case "mistral":
|
||||
case "asksage":
|
||||
case "xai":
|
||||
case "nebius":
|
||||
case "sambanova":
|
||||
case "cerebras":
|
||||
case "sapaicore":
|
||||
case "zai":
|
||||
default:
|
||||
updates.planModeApiModelId = sourceFields.apiModelId
|
||||
updates.actModeApiModelId = sourceFields.apiModelId
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
/* @import "tailwindcss/preflight.css" layer(base); */
|
||||
@import "tailwindcss/utilities.css" layer(utilities);
|
||||
|
||||
@config "../tailwind.config.mjs";
|
||||
|
||||
/* Import Azeret Mono font from local package */
|
||||
@import "@fontsource/azeret-mono/300.css";
|
||||
@import "@fontsource/azeret-mono/400.css";
|
||||
@import "@fontsource/azeret-mono/700.css";
|
||||
|
||||
@config "../tailwind.config.mjs";
|
||||
|
||||
textarea:focus {
|
||||
outline: 1.5px solid var(--vscode-focusBorder, #007fd4);
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export function validateModelId(
|
||||
const { apiProvider, openRouterModelId } = getModeSpecificFields(apiConfiguration, currentMode)
|
||||
switch (apiProvider) {
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
case "cline": {
|
||||
const modelId = openRouterModelId || openRouterDefaultModelId // in case the user hasn't changed the model id, it will be undefined by default
|
||||
if (!modelId) {
|
||||
return "You must provide a model ID."
|
||||
@@ -181,6 +181,7 @@ export function validateModelId(
|
||||
return "The model ID you provided is not available. Please choose a different model."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
|
||||
Reference in New Issue
Block a user