diff --git a/apps/web-evals/package.json b/apps/web-evals/package.json index 020d56fbd2d9..377401633231 100644 --- a/apps/web-evals/package.json +++ b/apps/web-evals/package.json @@ -8,7 +8,7 @@ "dev": "scripts/check-services.sh && next dev -p 3446", "format": "prettier --write src", "build": "next build", - "start": "next start", + "start": "next start -p 3446", "clean": "rimraf tsconfig.tsbuildinfo .next .turbo" }, "dependencies": { diff --git a/apps/web-roo-code/public/opengraph.png b/apps/web-roo-code/public/opengraph.png new file mode 100644 index 000000000000..fe75ec976b05 Binary files /dev/null and b/apps/web-roo-code/public/opengraph.png differ diff --git a/apps/web-roo-code/src/lib/seo.ts b/apps/web-roo-code/src/lib/seo.ts index 962662bb22c3..7dfad0550a22 100644 --- a/apps/web-roo-code/src/lib/seo.ts +++ b/apps/web-roo-code/src/lib/seo.ts @@ -3,15 +3,15 @@ const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://roocode.com" export const SEO = { url: SITE_URL, name: "Roo Code", - title: "Roo Code – Your AI-Powered Dev Team in VS Code", + title: "Roo Code – Your AI-Powered Dev Team in VS Code and Beyond", description: "Roo Code puts an entire AI dev team right in your editor, outpacing closed tools with deep project-wide context, multi-step agentic coding, and unmatched developer-centric flexibility.", locale: "en_US", ogImage: { - url: "/android-chrome-512x512.png", - width: 512, - height: 512, - alt: "Roo Code Logo", + url: "/opengraph.png", + width: 1200, + height: 600, + alt: "Roo Code", }, keywords: [ "Roo Code", diff --git a/packages/evals/Dockerfile.web b/packages/evals/Dockerfile.web index 4c6a8e0258cc..c578955232e7 100644 --- a/packages/evals/Dockerfile.web +++ b/packages/evals/Dockerfile.web @@ -60,5 +60,5 @@ RUN chmod +x /usr/local/bin/entrypoint.sh ENV DATABASE_URL=postgresql://postgres:password@db:5432/evals_development ENV REDIS_URL=redis://redis:6379 -EXPOSE 3000 +EXPOSE 3446 ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/packages/evals/README.md b/packages/evals/README.md index 095dae4861d6..5d1eaa336059 100644 --- a/packages/evals/README.md +++ b/packages/evals/README.md @@ -29,7 +29,7 @@ Start the evals service: pnpm evals ``` -The initial build process can take a minute or two. Upon success you should see output indicating that a web service is running on localhost:3000: +The initial build process can take a minute or two. Upon success you should see output indicating that a web service is running on localhost:3446: Additionally, you'll find in Docker Desktop that database and redis services are running: diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index be4a632042db..ea14b1decb77 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -515,6 +515,11 @@ export const providerSettingsSchema = z.object({ }) export type ProviderSettings = z.infer +export interface QuotaInfo { + total_quota?: number + used_quota?: number + is_star?: string +} export const providerSettingsWithIdSchema = providerSettingsSchema.extend({ id: z.string().optional() }) diff --git a/packages/types/src/providers/roo.ts b/packages/types/src/providers/roo.ts index 5bba18e2983d..01fae43cd57e 100644 --- a/packages/types/src/providers/roo.ts +++ b/packages/types/src/providers/roo.ts @@ -1,6 +1,10 @@ import type { ModelInfo } from "../model.js" -export type RooModelId = "xai/grok-code-fast-1" | "roo/code-supernova" +export type RooModelId = + | "xai/grok-code-fast-1" + | "roo/code-supernova" + | "xai/grok-4-fast" + | "deepseek/deepseek-chat-v3.1" export const rooDefaultModelId: RooModelId = "xai/grok-code-fast-1" @@ -25,4 +29,24 @@ export const rooModels = { description: "A versatile agentic coding stealth model that supports image inputs, accessible for free through Roo Code Cloud for a limited time. (Note: the free prompts and completions are logged by the model provider and used to improve the model.)", }, + "xai/grok-4-fast": { + maxTokens: 30_000, + contextWindow: 2_000_000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: + "Grok 4 Fast is xAI's latest multimodal model with SOTA cost-efficiency and a 2M token context window. (Note: prompts and completions are logged by xAI and used to improve the model.)", + }, + "deepseek/deepseek-chat-v3.1": { + maxTokens: 16_384, + contextWindow: 163_840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: + "DeepSeek-V3.1 is a large hybrid reasoning model (671B parameters, 37B active). It extends the DeepSeek-V3 base with a two-phase long-context training process, reaching up to 128K tokens, and uses FP8 microscaling for efficient inference.", + }, } as const satisfies Record diff --git a/packages/types/src/telemetry.ts b/packages/types/src/telemetry.ts index 31e4b31fdb31..c8d92636de4e 100644 --- a/packages/types/src/telemetry.ts +++ b/packages/types/src/telemetry.ts @@ -61,6 +61,9 @@ export enum TelemetryEventName { ACCOUNT_LOGOUT_CLICKED = "Account Logout Clicked", ACCOUNT_LOGOUT_SUCCESS = "Account Logout Success", + UPSELL_DISMISSED = "Upsell Dismissed", + UPSELL_CLICKED = "Upsell Clicked", + SCHEMA_VALIDATION_ERROR = "Schema Validation Error", DIFF_APPLICATION_ERROR = "Diff Application Error", SHELL_INTEGRATION_ERROR = "Shell Integration Error", @@ -187,6 +190,8 @@ export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [ TelemetryEventName.ACCOUNT_CONNECT_SUCCESS, TelemetryEventName.ACCOUNT_LOGOUT_CLICKED, TelemetryEventName.ACCOUNT_LOGOUT_SUCCESS, + TelemetryEventName.UPSELL_DISMISSED, + TelemetryEventName.UPSELL_CLICKED, TelemetryEventName.SCHEMA_VALIDATION_ERROR, TelemetryEventName.DIFF_APPLICATION_ERROR, TelemetryEventName.SHELL_INTEGRATION_ERROR, diff --git a/releases/3.28.8-release.png b/releases/3.28.8-release.png new file mode 100644 index 000000000000..8fcfa224538d Binary files /dev/null and b/releases/3.28.8-release.png differ diff --git a/src/api/providers/fetchers/zgsm.ts b/src/api/providers/fetchers/zgsm.ts index 9518ab32109c..d2acbade23f0 100644 --- a/src/api/providers/fetchers/zgsm.ts +++ b/src/api/providers/fetchers/zgsm.ts @@ -1,7 +1,7 @@ import axios from "axios" import { v7 as uuidv7 } from "uuid" import { COSTRICT_DEFAULT_HEADERS } from "../../../shared/headers" -import type { IZgsmModelResponseData } from "@roo-code/types" +import type { IZgsmModelResponseData, QuotaInfo } from "@roo-code/types" import { readModels } from "./modelCache" // let modelsCache = new WeakRef([]) @@ -44,3 +44,36 @@ export async function getZgsmModels(baseUrl?: string, apiKey?: string, openAiHea return Object.keys(modelCache).map((key) => modelCache[key]) } } + +export async function fetchZgsmQuotaInfo(baseUrl?: string, apiKey?: string): Promise { + try { + if (!baseUrl || !apiKey) { + return null + } + + // Trim whitespace from baseUrl to handle cases where users accidentally include spaces + const trimmedBaseUrl = baseUrl.trim() + + if (!URL.canParse(trimmedBaseUrl)) { + return null + } + + const config: Record = {} + const headers: Record = { + ...COSTRICT_DEFAULT_HEADERS, + "X-Request-ID": uuidv7(), + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/json", + } + + if (Object.keys(headers).length > 0) { + config["headers"] = headers + } + const response = await axios.get(`${baseUrl}/quota-manager/api/v1/quota`, config) + + return response?.data?.data as QuotaInfo + } catch (error) { + console.warn(`Error fetching zgsmModels from [${baseUrl}/quota-manager/api/v1/quota]:`, error.message) + return null + } +} diff --git a/src/core/condense/__tests__/index.spec.ts b/src/core/condense/__tests__/index.spec.ts index d86b500f9027..6a03298aa690 100644 --- a/src/core/condense/__tests__/index.spec.ts +++ b/src/core/condense/__tests__/index.spec.ts @@ -283,6 +283,32 @@ describe("summarizeConversation", () => { const mockCallArgs = (maybeRemoveImageBlocks as Mock).mock.calls[0][0] as any[] expect(mockCallArgs[mockCallArgs.length - 1]).toEqual(expectedFinalMessage) }) + it("should include the original first user message in summarization input", async () => { + const messages: ApiMessage[] = [ + { role: "user", content: "Initial ask", ts: 1 }, + { role: "assistant", content: "Ack", ts: 2 }, + { role: "user", content: "Follow-up", ts: 3 }, + { role: "assistant", content: "Response", ts: 4 }, + { role: "user", content: "More", ts: 5 }, + { role: "assistant", content: "Later", ts: 6 }, + { role: "user", content: "Newest", ts: 7 }, + ] + + await summarizeConversation(messages, mockApiHandler, defaultSystemPrompt, taskId, DEFAULT_PREV_CONTEXT_TOKENS) + + const mockCallArgs = (maybeRemoveImageBlocks as Mock).mock.calls[0][0] as any[] + + // Expect the original first user message to be present in the messages sent to the summarizer + const hasInitialAsk = mockCallArgs.some( + (m) => + m.role === "user" && + (typeof m.content === "string" + ? m.content === "Initial ask" + : Array.isArray(m.content) && + m.content.some((b: any) => b.type === "text" && b.text === "Initial ask")), + ) + expect(hasInitialAsk).toBe(true) + }) it("should calculate newContextTokens correctly with systemPrompt", async () => { const messages: ApiMessage[] = [ diff --git a/src/core/condense/index.ts b/src/core/condense/index.ts index 166a8ba4cad8..86cfa7ab1e5d 100644 --- a/src/core/condense/index.ts +++ b/src/core/condense/index.ts @@ -103,8 +103,8 @@ export async function summarizeConversation( // Always preserve the first message (which may contain slash command content) const firstMessage = messages[0] - // Get messages to summarize, excluding the first message and last N messages - const messagesToSummarize = getMessagesSinceLastSummary(messages.slice(1, -N_MESSAGES_TO_KEEP)) + // Get messages to summarize, including the first message and excluding the last N messages + const messagesToSummarize = getMessagesSinceLastSummary(messages.slice(0, -N_MESSAGES_TO_KEEP)) if (messagesToSummarize.length <= 1) { const error = diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index a4422c67d702..14bbfe2ca5fa 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap index 1f4e35bda992..3049a5b31ad5 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index 33491c9022df..45f27c3cf587 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index c03f345fbed7..b1925ec51e73 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index 6c1b73d5adfd..b8fc69643305 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index a4422c67d702..14bbfe2ca5fa 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index f6390c660146..35b587132a40 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index a4422c67d702..14bbfe2ca5fa 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index b248f0dc9991..7669ebcba86e 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index a4422c67d702..14bbfe2ca5fa 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index f6d753b01d19..6b7bd84874b8 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index c03f345fbed7..b1925ec51e73 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index a4422c67d702..14bbfe2ca5fa 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/prompts/sections/tool-use.ts b/src/core/prompts/sections/tool-use.ts index c598fabae34c..28d47d09858e 100644 --- a/src/core/prompts/sections/tool-use.ts +++ b/src/core/prompts/sections/tool-use.ts @@ -3,7 +3,7 @@ export function getSharedToolUseSection(): string { TOOL USE -You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. +You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. # Tool Use Formatting diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts index 15a1d64a4aba..4a5aef325c4b 100644 --- a/src/core/tools/multiApplyDiffTool.ts +++ b/src/core/tools/multiApplyDiffTool.ts @@ -464,7 +464,7 @@ Error: ${failPart.error} Suggested fixes: 1. Verify the search content exactly matches the file content (including whitespace and case) 2. Check for correct indentation and line endings -3. Use to see the current file content +3. Use the read_file tool to verify the file's current contents 4. Consider breaking complex changes into smaller diffs 5. Ensure start_line parameter matches the actual content location ${errorDetails ? `\nDetailed error information:\n${errorDetails}\n` : ""} @@ -477,7 +477,7 @@ Unable to apply diffs to file: ${absolutePath} Error: ${diffResult.error} Recovery suggestions: -1. Use to examine the current file content +1. Use the read_file tool to verify the file's current contents 2. Verify the diff format matches the expected search/replace pattern 3. Check that the search content exactly matches what's in the file 4. Consider using line numbers with start_line parameter diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 3f93d82d7ee6..29dfe6b6e234 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2305,6 +2305,18 @@ export class ClineProvider return } + // // Log out from cloud if authenticated + // if (CloudService.hasInstance()) { + // try { + // await CloudService.instance.logout() + // } catch (error) { + // this.log( + // `Failed to logout from cloud during reset: ${error instanceof Error ? error.message : String(error)}`, + // ) + // // Continue with reset even if logout fails + // } + // } + await this.contextProxy.resetAllState() await this.providerSettingsManager.resetAllConfigs() await this.customModesManager.resetCustomModes() diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 3bb246d05d44..c9b982b8bf25 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -70,6 +70,7 @@ import { ZgsmCodebaseIndexManager, IndexSwitchRequest, IndexStatusInfo } from ". import { ErrorCodeManager } from "../costrict/error-code" import { writeCostrictAccessToken } from "../costrict/codebase-index/utils" import { workspaceEventMonitor } from "../costrict/codebase-index/workspace-event-monitor" +import { fetchZgsmQuotaInfo } from "../../api/providers/fetchers/zgsm" export const webviewMessageHandler = async ( provider: ClineProvider, @@ -3430,5 +3431,21 @@ export const webviewMessageHandler = async ( }) break } + case "fetchZgsmQuotaInfo": { + const { apiConfiguration } = await provider.getState() + + // zgsmQuotaInfo + const data = await fetchZgsmQuotaInfo( + apiConfiguration.zgsmBaseUrl || ZgsmAuthConfig.getInstance().getDefaultApiBaseUrl(), + apiConfiguration.zgsmAccessToken, + ) + if (data) { + await provider.postMessageToWebview({ + type: "zgsmQuotaInfo", + values: data, + }) + } + break + } } } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index e14bc7171ac5..2e5337a8aeb8 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -84,6 +84,7 @@ export interface ExtensionMessage { | "zgsmLogined" | "showReauthConfirmationDialog" | "zgsmCodebaseIndexEnabled" + | "zgsmQuotaInfo" // zgsm | "ollamaModels" | "lmStudioModels" diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index fc2f148261e7..0bf647b713c7 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -219,6 +219,7 @@ export interface WebviewMessage { | "useZgsmCustomConfig" | "zgsmCodebaseIndexEnabled" | "showZgsmCodebaseDisableConfirmDialog" + | "fetchZgsmQuotaInfo" // zgsm | "profileThresholds" | "shareTaskSuccess" diff --git a/webview-ui/src/components/chat/ErrorRow.tsx b/webview-ui/src/components/chat/ErrorRow.tsx index 46da97798231..72720cdb16cf 100644 --- a/webview-ui/src/components/chat/ErrorRow.tsx +++ b/webview-ui/src/components/chat/ErrorRow.tsx @@ -101,7 +101,7 @@ export const ErrorRow = memo( }`} onClick={handleToggleExpand}>
- + {errorTitle}
@@ -130,20 +130,17 @@ export const ErrorRow = memo( <> {errorTitle && (
- - {errorTitle} + + {errorTitle}
)} {apiConfiguration.apiProvider !== "zgsm" ? ( -

+

{message}

) : (

diff --git a/webview-ui/src/components/cloud/CloudAccountSwitcher.tsx b/webview-ui/src/components/cloud/CloudAccountSwitcher.tsx index fbdeeaca3af6..0d46f0f5f552 100644 --- a/webview-ui/src/components/cloud/CloudAccountSwitcher.tsx +++ b/webview-ui/src/components/cloud/CloudAccountSwitcher.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react" -import { Building2 } from "lucide-react" +import { Building2, Plus } from "lucide-react" import { Select, SelectContent, SelectItem, SelectTrigger, SelectSeparator } from "@/components/ui/select" import { useAppTranslation } from "@src/i18n/TranslationContext" import { vscode } from "@src/utils/vscode" @@ -8,7 +8,7 @@ import { cn } from "@src/lib/utils" export const CloudAccountSwitcher = () => { const { t } = useAppTranslation() - const { cloudUserInfo, cloudOrganizations = [] } = useExtensionState() + const { cloudUserInfo, cloudOrganizations = [], cloudApiUrl } = useExtensionState() const [selectedOrgId, setSelectedOrgId] = useState(cloudUserInfo?.organizationId || null) const [isLoading, setIsLoading] = useState(false) @@ -17,12 +17,21 @@ export const CloudAccountSwitcher = () => { setSelectedOrgId(cloudUserInfo?.organizationId || null) }, [cloudUserInfo?.organizationId]) - // Don't show the switcher if user has no organizations - if (!cloudOrganizations || cloudOrganizations.length === 0 || !cloudUserInfo) { + // Show the switcher whenever user is authenticated + if (!cloudUserInfo) { return null } const handleOrganizationChange = async (value: string) => { + // Handle "Create Team Account" option + if (value === "create-team") { + if (cloudApiUrl) { + const billingUrl = `${cloudApiUrl}/billing` + vscode.postMessage({ type: "openExternal", url: billingUrl }) + } + return + } + const newOrgId = value === "personal" ? null : value // Don't do anything if selecting the same organization @@ -139,6 +148,19 @@ export const CloudAccountSwitcher = () => {
))} + + {/* Only show Create Team Account if user has no organizations */} + {cloudOrganizations.length === 0 && ( + <> + + +
+ + {t("cloud:createTeamAccount")} +
+
+ + )} diff --git a/webview-ui/src/components/cloud/CloudView.tsx b/webview-ui/src/components/cloud/CloudView.tsx index 0ec0042fe163..de5fdd003b1b 100644 --- a/webview-ui/src/components/cloud/CloudView.tsx +++ b/webview-ui/src/components/cloud/CloudView.tsx @@ -190,11 +190,13 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone, orga )} {/* Organization Switcher - moved below email */} - {organizations && organizations.length > 0 && ( -
- -
- )} +
+ +
)} diff --git a/webview-ui/src/components/cloud/OrganizationSwitcher.tsx b/webview-ui/src/components/cloud/OrganizationSwitcher.tsx index cbd564352d07..3c02bfe6ffe9 100644 --- a/webview-ui/src/components/cloud/OrganizationSwitcher.tsx +++ b/webview-ui/src/components/cloud/OrganizationSwitcher.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react" -import { Building2, User } from "lucide-react" +import { Building2, User, Plus } from "lucide-react" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator } from "@/components/ui/select" import { type CloudUserInfo, type CloudOrganizationMembership } from "@roo-code/types" import { useAppTranslation } from "@src/i18n/TranslationContext" @@ -10,9 +10,15 @@ type OrganizationSwitcherProps = { userInfo: CloudUserInfo organizations: CloudOrganizationMembership[] onOrganizationChange?: (organizationId: string | null) => void + cloudApiUrl?: string } -export const OrganizationSwitcher = ({ userInfo, organizations, onOrganizationChange }: OrganizationSwitcherProps) => { +export const OrganizationSwitcher = ({ + userInfo, + organizations, + onOrganizationChange, + cloudApiUrl, +}: OrganizationSwitcherProps) => { const { t } = useAppTranslation() const [selectedOrgId, setSelectedOrgId] = useState(userInfo.organizationId || null) const [isLoading, setIsLoading] = useState(false) @@ -45,6 +51,15 @@ export const OrganizationSwitcher = ({ userInfo, organizations, onOrganizationCh }, [userInfo.organizationId]) const handleOrganizationChange = async (value: string) => { + // Handle "Create Team Account" option + if (value === "create-team") { + if (cloudApiUrl) { + const billingUrl = `${cloudApiUrl}/billing` + vscode.postMessage({ type: "openExternal", url: billingUrl }) + } + return + } + const newOrgId = value === "personal" ? null : value // Don't do anything if selecting the same organization @@ -69,10 +84,7 @@ export const OrganizationSwitcher = ({ userInfo, organizations, onOrganizationCh } } - // If user has no organizations, don't show the switcher - if (!organizations || organizations.length === 0) { - return null - } + // Always show the switcher when user is authenticated const currentValue = selectedOrgId || "personal" @@ -139,6 +151,19 @@ export const OrganizationSwitcher = ({ userInfo, organizations, onOrganizationCh ))} + + {/* Only show Create Team Account if user has no organizations */} + {organizations.length === 0 && ( + <> + + +
+ + {t("cloud:createTeamAccount")} +
+
+ + )} diff --git a/webview-ui/src/components/cloud/ZgsmAccountView.tsx b/webview-ui/src/components/cloud/ZgsmAccountView.tsx index 0a6997b43f38..790f2dee5e76 100644 --- a/webview-ui/src/components/cloud/ZgsmAccountView.tsx +++ b/webview-ui/src/components/cloud/ZgsmAccountView.tsx @@ -1,7 +1,7 @@ -import { useCallback } from "react" +import { useCallback, useEffect, useState } from "react" import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" -import type { ProviderSettings } from "@roo-code/types" +import type { ProviderSettings, QuotaInfo } from "@roo-code/types" import { TelemetryEventName } from "@roo-code/types" import { useAppTranslation } from "@src/i18n/TranslationContext" @@ -18,6 +18,7 @@ type AccountViewProps = { export const ZgsmAccountView = ({ apiConfiguration, onDone }: AccountViewProps) => { const { t } = useAppTranslation() + const [quotaInfo, setQuotaInfo] = useState() const { userInfo, logoPic, hash } = useZgsmUserInfo(apiConfiguration?.zgsmAccessToken) console.log("New Credit hash: ", hash) const rooLogoUri = (window as any).COSTRICT_BASE_URI + "/logo.svg" @@ -52,17 +53,31 @@ export const ZgsmAccountView = ({ apiConfiguration, onDone }: AccountViewProps) onDone() break } + case "zgsmQuotaInfo": { + setQuotaInfo(message?.values) + break + } } }, [onDone], ) + useEffect(() => { + const timer = setInterval(async () => { + vscode.postMessage({ type: "fetchZgsmQuotaInfo" }) + }, 10_000) + vscode.postMessage({ type: "fetchZgsmQuotaInfo" }) + return () => { + clearInterval(timer) + } + }, []) + useEvent("message", onMessage) return ( -
+
-

{t("cloud:title")}

+

{t("cloud:title")}

{t("settings:common.done")} @@ -70,8 +85,8 @@ export const ZgsmAccountView = ({ apiConfiguration, onDone }: AccountViewProps) {apiConfiguration?.zgsmAccessToken ? ( <> {userInfo && ( -
-
+
+
{logoPic ? ( ) : ( -
+
{userInfo?.name?.charAt(0) || userInfo?.email?.charAt(0) || "?"}
)}
{userInfo.name && ( -

{userInfo.name}

+

+ {userInfo.name} +

)} {userInfo?.email && ( -

{userInfo?.email}

+

{userInfo?.email}

)} {userInfo?.organizationName && ( -
+
{userInfo.organizationImageUrl && ( {userInfo.organizationName} )} {userInfo.organizationName}
)} + {quotaInfo && (quotaInfo.total_quota || quotaInfo.used_quota) && ( +
+
+
+ + {t("cloud:quota.usageRate")} + + + {quotaInfo.used_quota && quotaInfo.total_quota + ? `${Math.round((quotaInfo.used_quota / quotaInfo.total_quota) * 100)}%` + : "0%"} + +
+
+
+
+
+
+
+ +
+
+
+
+ + {t("cloud:quota.totalQuota")} + +
+
+ + {quotaInfo.total_quota + ? quotaInfo.total_quota.toLocaleString() + : "-"} + + {quotaInfo.total_quota && ( + + Credit + + )} +
+
+ +
+
+
+ + {t("cloud:quota.usedQuota")} + +
+
+ + {quotaInfo.used_quota ? quotaInfo.used_quota.toLocaleString() : "-"} + + {quotaInfo.used_quota && ( + + Credit + + )} +
+
+
+
+ )}
)}
@@ -120,10 +213,10 @@ export const ZgsmAccountView = ({ apiConfiguration, onDone }: AccountViewProps) ) : ( <> -
-
+
+
- Costrict logo + Costrict logo
-
- -
-

{t("account:signIn")}

+

{t("account:signIn")}

+

??????????????

diff --git a/webview-ui/src/components/common/DismissibleUpsell.tsx b/webview-ui/src/components/common/DismissibleUpsell.tsx index 2eefb89972ef..6f0e1ad2dce4 100644 --- a/webview-ui/src/components/common/DismissibleUpsell.tsx +++ b/webview-ui/src/components/common/DismissibleUpsell.tsx @@ -1,6 +1,8 @@ import { memo, ReactNode, useEffect, useState, useRef } from "react" import { vscode } from "@src/utils/vscode" import { useAppTranslation } from "@src/i18n/TranslationContext" +import { telemetryClient } from "@src/utils/TelemetryClient" +import { TelemetryEventName } from "@roo-code/types" interface DismissibleUpsellProps { /** Required unique identifier for this upsell */ @@ -76,7 +78,12 @@ const DismissibleUpsell = memo( } }, [upsellId]) - const handleDismiss = async () => { + const handleDismiss = () => { + // Track telemetry for dismissal + telemetryClient.capture(TelemetryEventName.UPSELL_DISMISSED, { + upsellId: upsellId, + }) + // First notify the extension to persist the dismissal // This ensures the message is sent even if the component unmounts quickly vscode.postMessage({ @@ -134,6 +141,13 @@ const DismissibleUpsell = memo(
{ + // Track telemetry for click + if (onClick) { + telemetryClient.capture(TelemetryEventName.UPSELL_CLICKED, { + upsellId: upsellId, + }) + } + // Call the onClick handler if provided onClick?.() // Also dismiss if dismissOnClick is true diff --git a/webview-ui/src/components/common/__tests__/DismissibleUpsell.spec.tsx b/webview-ui/src/components/common/__tests__/DismissibleUpsell.spec.tsx index 3af66dfdf189..69e4e94a4f5c 100644 --- a/webview-ui/src/components/common/__tests__/DismissibleUpsell.spec.tsx +++ b/webview-ui/src/components/common/__tests__/DismissibleUpsell.spec.tsx @@ -1,6 +1,7 @@ import { render, screen, fireEvent, waitFor, act } from "@testing-library/react" import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" import DismissibleUpsell from "../DismissibleUpsell" +import { TelemetryEventName } from "@roo-code/types" // Mock the vscode API const mockPostMessage = vi.fn() @@ -10,6 +11,14 @@ vi.mock("@src/utils/vscode", () => ({ }, })) +// Mock telemetryClient +const mockCapture = vi.fn() +vi.mock("@src/utils/TelemetryClient", () => ({ + telemetryClient: { + capture: (eventName: string, properties?: Record) => mockCapture(eventName, properties), + }, +})) + // Mock the translation hook vi.mock("@src/i18n/TranslationContext", () => ({ useAppTranslation: () => ({ @@ -26,6 +35,7 @@ vi.mock("@src/i18n/TranslationContext", () => ({ describe("DismissibleUpsell", () => { beforeEach(() => { mockPostMessage.mockClear() + mockCapture.mockClear() vi.clearAllTimers() }) @@ -72,7 +82,7 @@ describe("DismissibleUpsell", () => { }) }) - it("hides the upsell when dismiss button is clicked", async () => { + it("hides the upsell when dismiss button is clicked and tracks telemetry", async () => { const onDismiss = vi.fn() const { container } = render( @@ -92,6 +102,11 @@ describe("DismissibleUpsell", () => { const dismissButton = screen.getByRole("button", { name: /dismiss/i }) fireEvent.click(dismissButton) + // Check that telemetry was tracked + expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_DISMISSED, { + upsellId: "test-upsell", + }) + // Check that the dismiss message was sent BEFORE hiding expect(mockPostMessage).toHaveBeenCalledWith({ type: "dismissUpsell", @@ -351,7 +366,7 @@ describe("DismissibleUpsell", () => { }) }) - it("calls onClick when the container is clicked", async () => { + it("calls onClick when the container is clicked and tracks telemetry", async () => { const onClick = vi.fn() render( @@ -372,6 +387,11 @@ describe("DismissibleUpsell", () => { fireEvent.click(container) expect(onClick).toHaveBeenCalledTimes(1) + + // Check that telemetry was tracked + expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_CLICKED, { + upsellId: "test-upsell", + }) }) it("does not call onClick when dismiss button is clicked", async () => { @@ -470,7 +490,7 @@ describe("DismissibleUpsell", () => { }) }) - it("dismisses when clicked if dismissOnClick is true", async () => { + it("dismisses when clicked if dismissOnClick is true and tracks both telemetry events", async () => { const onClick = vi.fn() const onDismiss = vi.fn() const { container } = render( @@ -493,6 +513,14 @@ describe("DismissibleUpsell", () => { expect(onClick).toHaveBeenCalledTimes(1) expect(onDismiss).toHaveBeenCalledTimes(1) + // Check that both telemetry events were tracked + expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_CLICKED, { + upsellId: "test-upsell", + }) + expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_DISMISSED, { + upsellId: "test-upsell", + }) + expect(mockPostMessage).toHaveBeenCalledWith({ type: "dismissUpsell", upsellId: "test-upsell", @@ -503,6 +531,46 @@ describe("DismissibleUpsell", () => { }) }) + it("dismisses on container click when dismissOnClick is true and no onClick is provided; tracks only dismissal", async () => { + const onDismiss = vi.fn() + const { container } = render( + +
Test content
+
, + ) + + // Make component visible + makeUpsellVisible() + + // Wait for component to be visible + await waitFor(() => { + expect(screen.getByText("Test content")).toBeInTheDocument() + }) + + // Click on the container (not the dismiss button) + const containerDiv = screen.getByText("Test content").parentElement as HTMLElement + fireEvent.click(containerDiv) + + // onDismiss should be called + expect(onDismiss).toHaveBeenCalledTimes(1) + + // Telemetry: only dismissal should be tracked + expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_DISMISSED, { + upsellId: "test-upsell", + }) + expect(mockCapture).not.toHaveBeenCalledWith(TelemetryEventName.UPSELL_CLICKED, expect.anything()) + + // Dismiss message should be sent + expect(mockPostMessage).toHaveBeenCalledWith({ + type: "dismissUpsell", + upsellId: "test-upsell", + }) + + // Component should be hidden + await waitFor(() => { + expect(container.firstChild).toBeNull() + }) + }) it("does not dismiss when clicked if dismissOnClick is false", async () => { const onClick = vi.fn() const onDismiss = vi.fn() diff --git a/webview-ui/src/components/settings/providers/Requesty.tsx b/webview-ui/src/components/settings/providers/Requesty.tsx index 2f531a0eccca..82e4ef8e42ca 100644 --- a/webview-ui/src/components/settings/providers/Requesty.tsx +++ b/webview-ui/src/components/settings/providers/Requesty.tsx @@ -36,8 +36,6 @@ export const Requesty = ({ }: RequestyProps) => { const { t } = useAppTranslation() - const [didRefetch, setDidRefetch] = useState() - const [requestyEndpointSelected, setRequestyEndpointSelected] = useState(!!apiConfiguration.requestyBaseUrl) // This ensures that the "Use custom URL" checkbox is hidden when the user deletes the URL. @@ -131,18 +129,12 @@ export const Requesty = ({ onClick={() => { vscode.postMessage({ type: "flushRouterModels", text: "requesty" }) refetchRouterModels() - setDidRefetch(true) }}>
{t("settings:providers.refreshModels.label")}
- {didRefetch && ( -
- {t("settings:providers.refreshModels.hint")} -
- )} Learn more.", "longRunningTask": "This might take a while. Continue from anywhere with Cloud.", diff --git a/webview-ui/src/i18n/locales/zh-CN/cloud.json b/webview-ui/src/i18n/locales/zh-CN/cloud.json index babeef6d6720..0f89326a4cd0 100644 --- a/webview-ui/src/i18n/locales/zh-CN/cloud.json +++ b/webview-ui/src/i18n/locales/zh-CN/cloud.json @@ -24,7 +24,13 @@ "startOver": "重新开始", "personalAccount": "个人账户", "switchAccount": "切换 Roo Code Cloud 账户", + "createTeamAccount": "创建团队账户", "cloudUrlPillLabel": "Roo Code Cloud URL", + "quota": { + "totalQuota": "总配额", + "usedQuota": "已使用", + "usageRate": "配额用量" + }, "upsell": { "autoApprovePowerUser": "给 Roo 一些独立性?使用 Roo Code Cloud 从任何地方控制它。 了解更多。", "longRunningTask": "这可能需要一段时间。使用 Cloud 从任何地方继续。", diff --git a/webview-ui/src/i18n/locales/zh-TW/cloud.json b/webview-ui/src/i18n/locales/zh-TW/cloud.json index 9ea24e23712c..fc1b79318a4d 100644 --- a/webview-ui/src/i18n/locales/zh-TW/cloud.json +++ b/webview-ui/src/i18n/locales/zh-TW/cloud.json @@ -24,7 +24,13 @@ "startOver": "重新開始", "personalAccount": "個人帳戶", "switchAccount": "切換 Roo Code Cloud 帳戶", + "createTeamAccount": "創建團隊帳戶", "cloudUrlPillLabel": "Roo Code Cloud URL", + "quota": { + "totalQuota": "總配額", + "usedQuota": "已使用", + "usageRate": "配額用量" + }, "upsell": { "autoApprovePowerUser": "給 Roo 一點獨立性?使用 Roo Code Cloud 隨時隨地控制它。了解更多。", "longRunningTask": "這可能需要一些時間。使用雲端隨時隨地繼續。",