diff --git a/.roo/commands/release.md b/.roo/commands/release.md
index ec54b804d1..702c5ba892 100644
--- a/.roo/commands/release.md
+++ b/.roo/commands/release.md
@@ -4,7 +4,7 @@ argument-hint: patch | minor | major
---
1. Identify the SHA corresponding to the most recent release using GitHub CLI: `gh release view --json tagName,targetCommitish,publishedAt`
-2. Analyze changes since the last release using: `gh pr list --state merged --json number,title,author,url,mergedAt,closingIssuesReferences --limit 1000 -q '[.[] | select(.mergedAt > "TIMESTAMP") | {number, title, author: .author.login, url, mergedAt, issues: .closingIssuesReferences}] | sort_by(.number)'`
+2. Analyze changes since the last release using: `gh pr list --state merged --base main --json number,title,author,url,mergedAt,closingIssuesReferences --limit 1000 -q '[.[] | select(.mergedAt > "TIMESTAMP") | {number, title, author: .author.login, url, mergedAt, issues: .closingIssuesReferences}] | sort_by(.number)'`
3. For each PR with linked issues, fetch the issue details to get the issue reporter: `gh issue view ISSUE_NUMBER --json number,author -q '{number, reporter: .author.login}'`
4. Summarize the changes. If the user did not specify, ask them whether this should be a major, minor, or patch release.
5. Create a changeset in .changeset/v[version].md instead of directly modifying package.json. The format is:
diff --git a/.roo/roomotes.yml b/.roo/roomotes.yml
index af0cd85b40..33f6b3bd57 100644
--- a/.roo/roomotes.yml
+++ b/.roo/roomotes.yml
@@ -20,14 +20,6 @@ github_events:
- event: pull_request.opened
action:
name: github.pr.review
- - event: pull_request.opened
- action:
- name: general.task
- prompt: |
- 1. Run the script `node scripts/find-missing-translations.js` and carefully review its output for any missing translations.
- 2. If the script reports missing translations, switch into `translate` mode and add them in all supported languages.
- 3. If you've added new translations, commit and push them to the existing PR.
- 4. If you get a permission error trying to push to the PR just give up (i.e don't create a new PR instead).
- event: pull_request_review_comment.created
action:
name: github.pr.comment.respond
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 777d1eab0d..1c97f6fcb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,52 @@
# Roo Code Changelog
+## [3.25.15] - 2025-08-14
+
+- Fix: Remove 500-message limit to prevent scrollbar jumping in long conversations (#7052, #7063 by @daniel-lxs, PR by @app/roomote)
+- Fix: Reset condensing state when switching tasks (#6919 by @f14XuanLv, PR by @f14XuanLv)
+- Fix: Implement sitemap generation in TypeScript and remove XML file (#5231 by @abumalick, PR by @abumalick)
+- Fix: allowedMaxRequests and allowedMaxCost values not showing in the settings UI (thanks @chrarnoldus!)
+
+## [3.25.14] - 2025-08-13
+
+- Fix: Only include verbosity parameter for models that support it (#7054 by @eastonmeth, PR by @app/roomote)
+- Fix: AWS Bedrock 1M context - Move anthropic_beta to additionalModelRequestFields (thanks @daniel-lxs!)
+- Fix: Make cancelling requests more responsive by reverting recent changes
+
+## [3.25.13] - 2025-08-12
+
+- Add Sonnet 1M context checkbox to Bedrock
+- Fix: add --no-messages flag to ripgrep to suppress file access errors (#6756 by @R-omk, PR by @app/roomote)
+- Add support for AGENT.md alongside AGENTS.md (#6912 by @Brendan-Z, PR by @app/roomote)
+- Remove deprecated GPT-4.5 Preview model (thanks @PeterDaveHello!)
+
+## [3.25.12] - 2025-08-12
+
+- Update: Claude Sonnet 4 context window configurable to 1 million tokens in Anthropic provider (thanks @daniel-lxs!)
+- Add: Minimal reasoning support to OpenRouter (thanks @daniel-lxs!)
+- Fix: Add configurable API request timeout for local providers (#6521 by @dabockster, PR by @app/roomote)
+- Fix: Add --no-sandbox flag to browser launch options (#6632 by @QuinsZouls, PR by @QuinsZouls)
+- Fix: Ensure JSON files respect .rooignore during indexing (#6690 by @evermoving, PR by @app/roomote)
+- Add: New Chutes provider models (#6698 by @fstandhartinger, PR by @app/roomote)
+- Add: OpenAI gpt-oss models to Amazon Bedrock dropdown (#6752 by @josh-clanton-powerschool, PR by @app/roomote)
+- Fix: Correct tool repetition detector to not block first tool call when limit is 1 (#6834 by @NaccOll, PR by @app/roomote)
+- Fix: Improve checkpoint service initialization handling (thanks @NaccOll!)
+- Update: Improve zh-TW Traditional Chinese locale (thanks @PeterDaveHello!)
+- Add: Task expand and collapse translations (thanks @app/roomote!)
+- Update: Exclude GPT-5 models from 20% context window output token cap (thanks @app/roomote!)
+- Fix: Truncate long model names in model selector to prevent overflow (thanks @app/roomote!)
+- Add: Requesty base url support (thanks @requesty-JohnCosta27!)
+
+## [3.25.11] - 2025-08-11
+
+- Add: Native OpenAI provider support for Codex Mini model (#5386 by @KJ7LNW, PR by @daniel-lxs)
+- Add: IO Intelligence Provider support (thanks @ertan2002!)
+- Fix: MCP startup issues and remove refresh notifications (thanks @hannesrudolph!)
+- Fix: Improvements to GPT-5 OpenAI provider configuration (thanks @hannesrudolph!)
+- Fix: Clarify codebase_search path parameter as optional and improve tool descriptions (thanks @app/roomote!)
+- Fix: Bedrock provider workaround for LiteLLM passthrough issues (thanks @jr!)
+- Fix: Token usage and cost being underreported on cancelled requests (thanks @chrarnoldus!)
+
## [3.25.10] - 2025-08-07
- Add support for GPT-5 (thanks Cline and @app/roomote!)
diff --git a/README.md b/README.md
index 1b5e8fe2ec..2f3a6069d9 100644
--- a/README.md
+++ b/README.md
@@ -208,46 +208,47 @@ Thanks to all our contributors who have helped make Roo Code better!
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/apps/web-roo-code/.env.example b/apps/web-roo-code/.env.example
index 734a9a2a08..01258bc500 100644
--- a/apps/web-roo-code/.env.example
+++ b/apps/web-roo-code/.env.example
@@ -6,3 +6,7 @@ NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
# Basin Form Endpoint for Static Form Submissions
# Replace this with your actual Basin form endpoint (e.g., https://usebasin.com/f/your-form-id)
NEXT_PUBLIC_BASIN_ENDPOINT=https://usebasin.com/f/your-form-id-here
+
+# Site URL Configuration
+# Used for generating absolute URLs in sitemap and other contexts
+NEXT_PUBLIC_SITE_URL=https://roocode.com
diff --git a/apps/web-roo-code/.gitignore b/apps/web-roo-code/.gitignore
index 7b8da95f5e..6f31e99c00 100644
--- a/apps/web-roo-code/.gitignore
+++ b/apps/web-roo-code/.gitignore
@@ -40,3 +40,7 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+# generated files
+/public/sitemap*.xml
+/public/robots.txt
diff --git a/apps/web-roo-code/.npmrc b/apps/web-roo-code/.npmrc
new file mode 100644
index 0000000000..b7425b9ee8
--- /dev/null
+++ b/apps/web-roo-code/.npmrc
@@ -0,0 +1 @@
+enable-pre-post-scripts=true
\ No newline at end of file
diff --git a/apps/web-roo-code/next-sitemap.config.cjs b/apps/web-roo-code/next-sitemap.config.cjs
new file mode 100644
index 0000000000..e9b0ca3c47
--- /dev/null
+++ b/apps/web-roo-code/next-sitemap.config.cjs
@@ -0,0 +1,73 @@
+/** @type {import('next-sitemap').IConfig} */
+module.exports = {
+ siteUrl: process.env.NEXT_PUBLIC_SITE_URL || 'https://roocode.com',
+ generateRobotsTxt: true,
+ generateIndexSitemap: false, // We don't need index sitemap for a small site
+ changefreq: 'monthly',
+ priority: 0.7,
+ sitemapSize: 5000,
+ exclude: [
+ '/api/*',
+ '/server-sitemap-index.xml',
+ '/404',
+ '/500',
+ '/_not-found',
+ ],
+ robotsTxtOptions: {
+ policies: [
+ {
+ userAgent: '*',
+ allow: '/',
+ },
+ ],
+ additionalSitemaps: [
+ // Add any additional sitemaps here if needed in the future
+ ],
+ },
+ // Custom transform function to set specific priorities and change frequencies
+ transform: async (config, path) => {
+ // Set custom priority for specific pages
+ let priority = config.priority;
+ let changefreq = config.changefreq;
+
+ if (path === '/') {
+ priority = 1.0;
+ changefreq = 'yearly';
+ } else if (path === '/enterprise' || path === '/evals') {
+ priority = 0.8;
+ changefreq = 'monthly';
+ } else if (path === '/privacy' || path === '/terms') {
+ priority = 0.5;
+ changefreq = 'yearly';
+ }
+
+ return {
+ loc: path,
+ changefreq,
+ priority,
+ lastmod: config.autoLastmod ? new Date().toISOString() : undefined,
+ alternateRefs: config.alternateRefs ?? [],
+ };
+ },
+ additionalPaths: async (config) => {
+ // Add any additional paths that might not be automatically discovered
+ // This is useful for dynamic routes or API-generated pages
+ // Add the /evals page since it's a dynamic route
+ return [{
+ loc: '/evals',
+ changefreq: 'monthly',
+ priority: 0.8,
+ lastmod: new Date().toISOString(),
+ }];
+
+ // Add the /evals page since it's a dynamic route
+ result.push({
+ loc: '/evals',
+ changefreq: 'monthly',
+ priority: 0.8,
+ lastmod: new Date().toISOString(),
+ });
+
+ return result;
+ },
+};
\ No newline at end of file
diff --git a/apps/web-roo-code/package.json b/apps/web-roo-code/package.json
index 8495dd961a..fa59cacccb 100644
--- a/apps/web-roo-code/package.json
+++ b/apps/web-roo-code/package.json
@@ -7,6 +7,7 @@
"check-types": "tsc --noEmit",
"dev": "next dev",
"build": "next build",
+ "postbuild": "next-sitemap --config next-sitemap.config.cjs",
"start": "next start",
"clean": "rimraf .next .turbo"
},
@@ -42,6 +43,7 @@
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"autoprefixer": "^10.4.21",
+ "next-sitemap": "^4.2.3",
"postcss": "^8.5.4",
"tailwindcss": "^3.4.17"
}
diff --git a/apps/web-roo-code/src/app/enterprise/page.tsx b/apps/web-roo-code/src/app/enterprise/page.tsx
index ca15857ffd..7a1d36f068 100644
--- a/apps/web-roo-code/src/app/enterprise/page.tsx
+++ b/apps/web-roo-code/src/app/enterprise/page.tsx
@@ -5,6 +5,54 @@ import { AnimatedText } from "@/components/animated-text"
import { AnimatedBackground } from "@/components/homepage"
import { ContactForm } from "@/components/enterprise/contact-form"
import { EXTERNAL_LINKS } from "@/lib/constants"
+import type { Metadata } from "next"
+import { SEO } from "@/lib/seo"
+
+const TITLE = "Enterprise Solution"
+const DESCRIPTION =
+ "The control-plane for AI-powered software development. Gain visibility, governance, and control over your AI coding initiatives."
+const PATH = "/enterprise"
+const OG_IMAGE = SEO.ogImage
+
+export const metadata: Metadata = {
+ title: TITLE,
+ description: DESCRIPTION,
+ alternates: {
+ canonical: `${SEO.url}${PATH}`,
+ },
+ openGraph: {
+ title: TITLE,
+ description: DESCRIPTION,
+ url: `${SEO.url}${PATH}`,
+ siteName: SEO.name,
+ images: [
+ {
+ url: OG_IMAGE.url,
+ width: OG_IMAGE.width,
+ height: OG_IMAGE.height,
+ alt: OG_IMAGE.alt,
+ },
+ ],
+ locale: SEO.locale,
+ type: "website",
+ },
+ twitter: {
+ card: SEO.twitterCard,
+ title: TITLE,
+ description: DESCRIPTION,
+ images: [OG_IMAGE.url],
+ },
+ keywords: [
+ ...SEO.keywords,
+ "Enterprise AI",
+ "AI governance",
+ "AI control-plane",
+ "developer productivity",
+ "SAML",
+ "SCIM",
+ "cost management",
+ ],
+}
export default async function Enterprise() {
return (
diff --git a/apps/web-roo-code/src/app/evals/page.tsx b/apps/web-roo-code/src/app/evals/page.tsx
index b97b025348..a6af30d70e 100644
--- a/apps/web-roo-code/src/app/evals/page.tsx
+++ b/apps/web-roo-code/src/app/evals/page.tsx
@@ -1,25 +1,45 @@
import type { Metadata } from "next"
import { getEvalRuns } from "@/actions/evals"
+import { SEO } from "@/lib/seo"
import { Evals } from "./evals"
export const revalidate = 300
export const dynamic = "force-dynamic"
+const TITLE = "Evals"
+const DESCRIPTION = "Explore quantitative evals of LLM coding skills across tasks and providers."
+const PATH = "/evals"
+const IMAGE = {
+ url: "https://i.imgur.com/ijP7aZm.png",
+ width: 1954,
+ height: 1088,
+ alt: "Roo Code Evals – LLM coding benchmarks",
+}
+
export const metadata: Metadata = {
- title: "Roo Code Evals",
- openGraph: {
- title: "Roo Code Evals",
- description: "Quantitative evals of LLM coding skills.",
- url: "https://roocode.com/evals",
- siteName: "Roo Code",
- images: {
- url: "https://i.imgur.com/ijP7aZm.png",
- width: 1954,
- height: 1088,
- },
+ title: TITLE,
+ description: DESCRIPTION,
+ alternates: {
+ canonical: `${SEO.url}${PATH}`,
},
+ openGraph: {
+ title: TITLE,
+ description: DESCRIPTION,
+ url: `${SEO.url}${PATH}`,
+ siteName: SEO.name,
+ images: [IMAGE],
+ locale: SEO.locale,
+ type: "website",
+ },
+ twitter: {
+ card: SEO.twitterCard,
+ title: TITLE,
+ description: DESCRIPTION,
+ images: [IMAGE.url],
+ },
+ keywords: [...SEO.keywords, "benchmarks", "LLM evals", "coding evaluations", "model comparison"],
}
export default async function Page() {
diff --git a/apps/web-roo-code/src/app/layout.tsx b/apps/web-roo-code/src/app/layout.tsx
index 132a8b31d7..ac33c8920f 100644
--- a/apps/web-roo-code/src/app/layout.tsx
+++ b/apps/web-roo-code/src/app/layout.tsx
@@ -2,6 +2,7 @@ import React from "react"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import Script from "next/script"
+import { SEO } from "@/lib/seo"
import { Providers } from "@/components/providers"
@@ -12,11 +13,14 @@ import "./globals.css"
const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
- title: "Roo Code – Your AI-Powered Dev Team in VS Code",
- 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.",
+ metadataBase: new URL(SEO.url),
+ title: {
+ template: "%s | Roo Code",
+ default: SEO.title,
+ },
+ description: SEO.description,
alternates: {
- canonical: "https://roocode.com",
+ canonical: SEO.url,
},
icons: {
icon: [
@@ -40,6 +44,42 @@ export const metadata: Metadata = {
},
],
},
+ openGraph: {
+ title: SEO.title,
+ description: SEO.description,
+ url: SEO.url,
+ siteName: SEO.name,
+ images: [
+ {
+ url: SEO.ogImage.url,
+ width: SEO.ogImage.width,
+ height: SEO.ogImage.height,
+ alt: SEO.ogImage.alt,
+ },
+ ],
+ locale: SEO.locale,
+ type: "website",
+ },
+ twitter: {
+ card: SEO.twitterCard,
+ title: SEO.title,
+ description: SEO.description,
+ images: [SEO.ogImage.url],
+ },
+ robots: {
+ index: true,
+ follow: true,
+ googleBot: {
+ index: true,
+ follow: true,
+ "max-snippet": -1,
+ "max-image-preview": "large",
+ "max-video-preview": -1,
+ },
+ },
+ keywords: [...SEO.keywords],
+ applicationName: SEO.name,
+ category: SEO.category,
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
@@ -64,8 +104,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
`}
-
-
+
+
{children}
diff --git a/apps/web-roo-code/src/app/privacy/page.tsx b/apps/web-roo-code/src/app/privacy/page.tsx
index ea12094ebb..1812d18eff 100644
--- a/apps/web-roo-code/src/app/privacy/page.tsx
+++ b/apps/web-roo-code/src/app/privacy/page.tsx
@@ -1,9 +1,41 @@
-import { Metadata } from "next"
+import type { Metadata } from "next"
+import { SEO } from "@/lib/seo"
+
+const TITLE = "Privacy Policy"
+const DESCRIPTION =
+ "Privacy policy for Roo Code Cloud and marketing website. Learn how we handle your data and protect your privacy."
+const PATH = "/privacy"
+const OG_IMAGE = SEO.ogImage
export const metadata: Metadata = {
- title: "Privacy Policy - Roo Code",
- description:
- "Privacy policy for Roo Code Cloud and marketing website. Learn how we handle your data and protect your privacy.",
+ title: TITLE,
+ description: DESCRIPTION,
+ alternates: {
+ canonical: `${SEO.url}${PATH}`,
+ },
+ openGraph: {
+ title: TITLE,
+ description: DESCRIPTION,
+ url: `${SEO.url}${PATH}`,
+ siteName: SEO.name,
+ images: [
+ {
+ url: OG_IMAGE.url,
+ width: OG_IMAGE.width,
+ height: OG_IMAGE.height,
+ alt: OG_IMAGE.alt,
+ },
+ ],
+ locale: SEO.locale,
+ type: "article",
+ },
+ twitter: {
+ card: SEO.twitterCard,
+ title: TITLE,
+ description: DESCRIPTION,
+ images: [OG_IMAGE.url],
+ },
+ keywords: [...SEO.keywords, "privacy", "data protection", "GDPR", "security"],
}
export default function Privacy() {
diff --git a/apps/web-roo-code/src/app/robots.ts b/apps/web-roo-code/src/app/robots.ts
new file mode 100644
index 0000000000..fcdda5031e
--- /dev/null
+++ b/apps/web-roo-code/src/app/robots.ts
@@ -0,0 +1,13 @@
+import type { MetadataRoute } from "next"
+import { SEO } from "@/lib/seo"
+
+export default function robots(): MetadataRoute.Robots {
+ return {
+ rules: {
+ userAgent: "*",
+ allow: "/",
+ },
+ sitemap: `${SEO.url}/sitemap.xml`,
+ host: SEO.url,
+ }
+}
diff --git a/apps/web-roo-code/src/app/sitemap.xml b/apps/web-roo-code/src/app/sitemap.xml
deleted file mode 100644
index 43ac973306..0000000000
--- a/apps/web-roo-code/src/app/sitemap.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
- https://roocode.com/
- 2025-03-13T22:26:09Z
- yearly
- 1.0
-
-
diff --git a/apps/web-roo-code/src/app/terms/page.tsx b/apps/web-roo-code/src/app/terms/page.tsx
index 545e577457..f5aea3d663 100644
--- a/apps/web-roo-code/src/app/terms/page.tsx
+++ b/apps/web-roo-code/src/app/terms/page.tsx
@@ -1,9 +1,41 @@
-import { Metadata } from "next"
+import type { Metadata } from "next"
+import { SEO } from "@/lib/seo"
+
+const TITLE = "Terms of Service"
+const DESCRIPTION =
+ "Terms of Service for Roo Code Cloud. Learn about our service terms, commercial conditions, and legal framework."
+const PATH = "/terms"
+const OG_IMAGE = SEO.ogImage
export const metadata: Metadata = {
- title: "Terms of Service - Roo Code",
- description:
- "Terms of Service for Roo Code Cloud. Learn about our service terms, commercial conditions, and legal framework.",
+ title: TITLE,
+ description: DESCRIPTION,
+ alternates: {
+ canonical: `${SEO.url}${PATH}`,
+ },
+ openGraph: {
+ title: TITLE,
+ description: DESCRIPTION,
+ url: `${SEO.url}${PATH}`,
+ siteName: SEO.name,
+ images: [
+ {
+ url: OG_IMAGE.url,
+ width: OG_IMAGE.width,
+ height: OG_IMAGE.height,
+ alt: OG_IMAGE.alt,
+ },
+ ],
+ locale: SEO.locale,
+ type: "article",
+ },
+ twitter: {
+ card: SEO.twitterCard,
+ title: TITLE,
+ description: DESCRIPTION,
+ images: [OG_IMAGE.url],
+ },
+ keywords: [...SEO.keywords, "terms of service", "legal", "agreement", "subscription"],
}
export default function Terms() {
diff --git a/apps/web-roo-code/src/lib/seo.ts b/apps/web-roo-code/src/lib/seo.ts
new file mode 100644
index 0000000000..962662bb22
--- /dev/null
+++ b/apps/web-roo-code/src/lib/seo.ts
@@ -0,0 +1,30 @@
+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",
+ 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",
+ },
+ keywords: [
+ "Roo Code",
+ "AI coding agent",
+ "VS Code extension",
+ "AI pair programmer",
+ "software development",
+ "agentic coding",
+ "code refactoring",
+ "debugging",
+ ],
+ category: "technology",
+ twitterCard: "summary_large_image" as const,
+} as const
+
+export type SeoConfig = typeof SEO
diff --git a/knip.json b/knip.json
index 7ca8cfad7b..a847111981 100644
--- a/knip.json
+++ b/knip.json
@@ -7,7 +7,8 @@
"src/activate/**",
"src/workers/countTokens.ts",
"src/extension.ts",
- "scripts/**"
+ "scripts/**",
+ "apps/web-roo-code/next-sitemap.config.cjs"
],
"workspaces": {
"src": {
@@ -16,7 +17,7 @@
},
"webview-ui": {
"entry": ["src/index.tsx"],
- "project": ["src/**/*.{ts,tsx}"]
+ "project": ["src/**/*.{ts,tsx}", "../src/shared/*.ts"]
},
"packages/{build,cloud,evals,ipc,telemetry,types}": {
"project": ["src/**/*.ts"]
diff --git a/locales/ca/README.md b/locales/ca/README.md
index a2ba29f1f0..3a156e83a6 100644
--- a/locales/ca/README.md
+++ b/locales/ca/README.md
@@ -182,46 +182,47 @@ Gràcies a tots els nostres col·laboradors que han ajudat a millorar Roo Code!
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/de/README.md b/locales/de/README.md
index 296f5d45da..4856e9255b 100644
--- a/locales/de/README.md
+++ b/locales/de/README.md
@@ -182,46 +182,47 @@ Danke an alle unsere Mitwirkenden, die geholfen haben, Roo Code zu verbessern!
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/es/README.md b/locales/es/README.md
index 55e337ce2b..4a2298009f 100644
--- a/locales/es/README.md
+++ b/locales/es/README.md
@@ -182,46 +182,47 @@ Usamos [changesets](https://github.com/changesets/changesets) para versionar y p
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/fr/README.md b/locales/fr/README.md
index a21af64235..7ea8ed8bfa 100644
--- a/locales/fr/README.md
+++ b/locales/fr/README.md
@@ -182,46 +182,47 @@ Merci à tous nos contributeurs qui ont aidé à améliorer Roo Code !
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/hi/README.md b/locales/hi/README.md
index dcbb8dfff5..8e174bce24 100644
--- a/locales/hi/README.md
+++ b/locales/hi/README.md
@@ -182,46 +182,47 @@ Roo Code को बेहतर बनाने में मदद करने
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/id/README.md b/locales/id/README.md
index fae6572e7b..ebdc117626 100644
--- a/locales/id/README.md
+++ b/locales/id/README.md
@@ -176,46 +176,47 @@ Terima kasih kepada semua kontributor kami yang telah membantu membuat Roo Code
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/it/README.md b/locales/it/README.md
index a9237c46dc..70c034677b 100644
--- a/locales/it/README.md
+++ b/locales/it/README.md
@@ -182,46 +182,47 @@ Grazie a tutti i nostri contributori che hanno aiutato a migliorare Roo Code!
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/ja/README.md b/locales/ja/README.md
index 3650f18571..15cbde889e 100644
--- a/locales/ja/README.md
+++ b/locales/ja/README.md
@@ -182,46 +182,47 @@ Roo Codeの改善に貢献してくれたすべての貢献者に感謝します
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/ko/README.md b/locales/ko/README.md
index d5f26d1f4e..9453cdd389 100644
--- a/locales/ko/README.md
+++ b/locales/ko/README.md
@@ -182,46 +182,47 @@ Roo Code를 더 좋게 만드는 데 도움을 준 모든 기여자에게 감사
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/nl/README.md b/locales/nl/README.md
index a167b102fb..ec4e34f69e 100644
--- a/locales/nl/README.md
+++ b/locales/nl/README.md
@@ -182,46 +182,47 @@ Dank aan alle bijdragers die Roo Code beter hebben gemaakt!
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/pl/README.md b/locales/pl/README.md
index f4e67900d6..4424ef18ad 100644
--- a/locales/pl/README.md
+++ b/locales/pl/README.md
@@ -182,46 +182,47 @@ Dziękujemy wszystkim naszym współtwórcom, którzy pomogli ulepszyć Roo Code
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/pt-BR/README.md b/locales/pt-BR/README.md
index 3b2042fcc1..a8ab2c3890 100644
--- a/locales/pt-BR/README.md
+++ b/locales/pt-BR/README.md
@@ -182,46 +182,47 @@ Obrigado a todos os nossos contribuidores que ajudaram a tornar o Roo Code melho
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/ru/README.md b/locales/ru/README.md
index cfa3a843ef..84f6830e52 100644
--- a/locales/ru/README.md
+++ b/locales/ru/README.md
@@ -182,46 +182,47 @@ code --install-extension bin/roo-cline-.vsix
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/tr/README.md b/locales/tr/README.md
index ed099e92c7..579b7da4fc 100644
--- a/locales/tr/README.md
+++ b/locales/tr/README.md
@@ -182,46 +182,47 @@ Roo Code'u daha iyi hale getirmeye yardımcı olan tüm katkıda bulunanlara te
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/vi/README.md b/locales/vi/README.md
index 0c37d84ce8..b6a133b5c7 100644
--- a/locales/vi/README.md
+++ b/locales/vi/README.md
@@ -182,46 +182,47 @@ Cảm ơn tất cả những người đóng góp đã giúp cải thiện Roo C
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/zh-CN/README.md b/locales/zh-CN/README.md
index d308eb0304..dd2e654dbe 100644
--- a/locales/zh-CN/README.md
+++ b/locales/zh-CN/README.md
@@ -182,46 +182,47 @@ code --install-extension bin/roo-cline-.vsix
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md
index 9767d316ed..85bcf647bb 100644
--- a/locales/zh-TW/README.md
+++ b/locales/zh-TW/README.md
@@ -183,46 +183,47 @@ code --install-extension bin/roo-cline-.vsix
-| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
-| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
-| 
joemanley201 | 
jr | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
-| 
NyxJae | 
d-oit | 
elianiva | 
qdaxb | 
xyOz-dev | 
punkpeye |
-| 
wkordalski | 
chrarnoldus | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
-| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
-| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
Szpadel | 
lupuletic |
-| 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie | 
ChuKhaLi |
-| 
PeterDaveHello | 
aheizi | 
NaccOll | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
-| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
-| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
-| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
-| 
p12tic | 
gtaylor | 
aitoroses | 
ross | 
mr-ryan-james | 
heyseth |
-| 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite | 
ronyblum |
-| 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo | 
ashktn |
-| 
franekp | 
yt3trees | 
seedlord | 
bramburn | 
anton-otee | 
benzntech |
-| 
axkirillov | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk | 
GitlyHallows |
-| 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras | 
SplittyDev |
-| 
mdp | 
napter | 
philfung | 
axmo | 
bannzai | 
bbenshalom |
-| 
chris-garrett | 
dairui1 | 
dqroid | 
ershang-fireworks | 
janaki-sasidhar | 
forestyoo |
-| 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
asychin | 
amittell |
-| 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 | 
vladstudio | 
vivekfyi |
-| 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 | 
tgfjt | 
maekawataiki |
-| 
AlexandruSmirnov | 
nevermorec | 
PretzelVector | 
zetaloop | 
cdlliuy | 
user202729 |
-| 
takakoutso | 
student20880 | 
shubhamgupta731 | 
shohei-ihaya | 
shivamd1810 | 
shaybc |
-| 
sensei-woo | 
samir-nimbly | 
robertheadley | 
refactorthis | 
qingyuan1109 | 
pokutuna |
-| 
philipnext | 
village-way | 
oprstchn | 
nobu007 | 
mosleyit | 
moqimoqidea |
-| 
mlopezr | 
mecab | 
olup | 
lightrabbit | 
lhish | 
kohii |
-| 
kinandan | 
jwcraig | 
jues | 
DeXtroTip | 
pfitz | 
ExactDoug |
-| 
celestial-vault | 
linegel | 
edwin-truthsearch-io | 
EamonNerbonne | 
dbasclpy | 
dflatline |
-| 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth | 
thecolorblue | 
bogdan0083 |
-| 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron | 
andrewshu2000 | 
andreastempsch |
-| 
alasano | 
QuinsZouls | 
HadesArchitect | 
alarno | 
nexon33 | 
adilhafeez |
-| 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
samsilveira | 
01Rian |
-| 
RSO | 
RandalSchwartz | 
SECKainersdorfer | 
R-omk | 
pwilkin | 
Sarke |
-| 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 | 
Naam | 
kvokka | 
ecmasx |
-| 
mollux | 
marvijo-code | 
markijbema | 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth |
-| 
shtse8 | 
Rexarrior | 
kevinvandijk | 
KevinZhao | 
ksze | 
AyazKaan |
-| 
Juice10 | 
snova-jorgep | 
Fovty | 
Jdo300 | 
hesara | |
+| 
mrubens | 
saoudrizwan | 
cte | 
daniel-lxs | 
samhvw8 | 
hannesrudolph |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+| 
KJ7LNW | 
a8trejo | 
MuriloFP | 
ColemanRoo | 
canrobins13 | 
stea9499 |
+| 
jr | 
joemanley201 | 
System233 | 
nissa-seru | 
jquanton | 
roomote-agent |
+| 
NyxJae | 
d-oit | 
elianiva | 
chrarnoldus | 
qdaxb | 
xyOz-dev |
+| 
punkpeye | 
wkordalski | 
SannidhyaSah | 
sachasayan | 
Smartsheet-JB-Brown | 
monotykamary |
+| 
cannuri | 
feifei325 | 
zhangtony239 | 
shariqriazz | 
vigneshsubbiah16 | 
pugazhendhi-m |
+| 
lloydchang | 
liwilliam2021 | 
dtrugman | 
hassoncs | 
PeterDaveHello | 
Szpadel |
+| 
lupuletic | 
kiwina | 
Premshay | 
psv2522 | 
olweraltuve | 
diarmidmackenzie |
+| 
ChuKhaLi | 
NaccOll | 
aheizi | 
nbihan-mediware | 
noritaka1166 | 
RaySinner |
+| 
afshawnlotfi | 
dleffel | 
StevenTCramer | 
Ruakij | 
pdecat | 
kyle-apex |
+| 
emshvac | 
brunobergher | 
Lunchb0ne | 
SmartManoj | 
vagadiya | 
slytechnical |
+| 
dlab-anton | 
arthurauffray | 
upamune | 
NamesMT | 
taylorwilsdon | 
sammcj |
+| 
p12tic | 
gtaylor | 
aitoroses | 
benzntech | 
ross | 
mr-ryan-james |
+| 
heyseth | 
taisukeoe | 
avtc | 
eonghk | 
GOODBOY008 | 
kcwhite |
+| 
ronyblum | 
teddyOOXX | 
thill2323 | 
vincentsong | 
yongjer | 
zeozeozeo |
+| 
ashktn | 
franekp | 
yt3trees | 
seedlord | 
QuinsZouls | 
anton-otee |
+| 
axkirillov | 
bramburn | 
olearycrew | 
catrielmuller | 
devxpain | 
snoyiatk |
+| 
GitlyHallows | 
jcbdev | 
Chenjiayuan195 | 
julionav | 
KanTakahiro | 
kevint-cerebras |
+| 
SplittyDev | 
mdp | 
napter | 
philfung | 
dqroid | 
dairui1 |
+| 
chris-garrett | 
bbenshalom | 
bannzai | 
ershang-fireworks | 
f14XuanLv | 
janaki-sasidhar |
+| 
forestyoo | 
hatsu38 | 
hongzio | 
im47cn | 
shoopapa | 
axmo |
+| 
asychin | 
amittell | 
Yoshino-Yukitaro | 
Yikai-Liao | 
zxdvd | 
s97712 |
+| 
vladstudio | 
vivekfyi | 
HahaBill | 
tmsjngx0 | 
TGlide | 
Githubguy132010 |
+| 
tgfjt | 
maekawataiki | 
DeXtroTip | 
qingyuan1109 | 
refactorthis | 
robertheadley |
+| 
samir-nimbly | 
sensei-woo | 
shaybc | 
shivamd1810 | 
shohei-ihaya | 
shubhamgupta731 |
+| 
student20880 | 
takakoutso | 
user202729 | 
cdlliuy | 
zetaloop | 
PretzelVector |
+| 
nevermorec | 
jues | 
jwcraig | 
kinandan | 
kohii | 
lhish |
+| 
lightrabbit | 
olup | 
mecab | 
mlopezr | 
moqimoqidea | 
mosleyit |
+| 
nobu007 | 
oprstchn | 
village-way | 
philipnext | 
pokutuna | 
pfitz |
+| 
ExactDoug | 
celestial-vault | 
linegel | 
ertan2002 | 
edwin-truthsearch-io | 
EamonNerbonne |
+| 
dbasclpy | 
dflatline | 
Deon588 | 
dleen | 
CW-B-W | 
chadgauth |
+| 
thecolorblue | 
bogdan0083 | 
benashby | 
Atlogit | 
atlasgong | 
AntiMoron |
+| 
andrewshu2000 | 
andreastempsch | 
alasano | 
HadesArchitect | 
alarno | 
nexon33 |
+| 
adilhafeez | 
adamwlarson | 
adamhill | 
AMHesch | 
adambrand | 
abumalick |
+| 
AlexandruSmirnov | 
samsilveira | 
01Rian | 
RSO | 
RandalSchwartz | 
SECKainersdorfer |
+| 
R-omk | 
pwilkin | 
Sarke | 
PaperBoardOfficial | 
OlegOAndreev | 
niteshbalusu11 |
+| 
Naam | 
kvokka | 
ecmasx | 
mollux | 
marvijo-code | 
markijbema |
+| 
mamertofabian | 
monkeyDluffy6017 | 
libertyteeth | 
shtse8 | 
Rexarrior | 
kevinvandijk |
+| 
KevinZhao | 
ksze | 
AyazKaan | 
Juice10 | 
snova-jorgep | 
Fovty |
+| 
Jdo300 | 
hesara | | | | |
diff --git a/package.json b/package.json
index c1ddc68223..cdc8a1abca 100644
--- a/package.json
+++ b/package.json
@@ -24,15 +24,20 @@
"knip": "knip --include files",
"update-contributors": "node scripts/update-contributors.js",
"evals": "dotenvx run -f packages/evals/.env.development packages/evals/.env.local -- docker compose -f packages/evals/docker-compose.yml --profile server --profile runner up --build --scale runner=0",
- "link-workspace-packages": "node scripts/link-packages.js",
- "unlink-workspace-packages": "node scripts/link-packages.js --unlink"
+ "npm:publish:types": "pnpm --filter @roo-code/types npm:publish",
+ "link-workspace-packages": "tsx scripts/link-packages.ts",
+ "unlink-workspace-packages": "tsx scripts/link-packages.ts --unlink"
},
"devDependencies": {
"@changesets/cli": "^2.27.10",
"@dotenvx/dotenvx": "^1.34.0",
+ "@roo-code/config-typescript": "workspace:^",
+ "@types/glob": "^9.0.0",
+ "@types/node": "^24.1.0",
"@vscode/vsce": "3.3.2",
"esbuild": "^0.25.0",
"eslint": "^9.27.0",
+ "glob": "^11.0.3",
"husky": "^9.1.7",
"knip": "^5.44.4",
"lint-staged": "^16.0.0",
@@ -41,6 +46,7 @@
"ovsx": "0.10.4",
"prettier": "^3.4.2",
"rimraf": "^6.0.1",
+ "tsx": "^4.19.3",
"turbo": "^2.5.3",
"typescript": "^5.4.5"
},
diff --git a/packages/types/.gitignore b/packages/types/.gitignore
new file mode 100644
index 0000000000..ddf5f39ede
--- /dev/null
+++ b/packages/types/.gitignore
@@ -0,0 +1,2 @@
+dist
+npm/package.json
diff --git a/packages/types/eslint.config.mjs b/packages/types/eslint.config.mjs
index 694bf73664..c603a68f12 100644
--- a/packages/types/eslint.config.mjs
+++ b/packages/types/eslint.config.mjs
@@ -1,4 +1,20 @@
import { config } from "@roo-code/config-eslint/base"
+import globals from "globals"
/** @type {import("eslint").Linter.Config} */
-export default [...config]
+export default [
+ ...config,
+ {
+ files: ["**/*.cjs"],
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ ...globals.commonjs,
+ },
+ sourceType: "commonjs",
+ },
+ rules: {
+ "@typescript-eslint/no-require-imports": "off",
+ },
+ },
+]
diff --git a/packages/types/npm/package.json b/packages/types/npm/package.metadata.json
similarity index 52%
rename from packages/types/npm/package.json
rename to packages/types/npm/package.metadata.json
index f73a83a7b6..391d2acbaa 100644
--- a/packages/types/npm/package.json
+++ b/packages/types/npm/package.metadata.json
@@ -1,6 +1,6 @@
{
"name": "@roo-code/types",
- "version": "1.44.0",
+ "version": "1.48.0",
"description": "TypeScript type definitions for Roo Code.",
"publishConfig": {
"access": "public",
@@ -17,28 +17,6 @@
"url": "https://github.com/RooCodeInc/Roo-Code/issues"
},
"homepage": "https://roocode.com",
- "keywords": [
- "roo",
- "roo-code",
- "ai"
- ],
- "main": "./dist/index.cjs",
- "module": "./dist/index.js",
- "types": "./dist/index.d.ts",
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.js",
- "require": {
- "types": "./dist/index.d.cts",
- "default": "./dist/index.cjs"
- }
- }
- },
- "files": [
- "dist"
- ],
- "dependencies": {
- "zod": "^3.25.61"
- }
+ "keywords": ["roo", "roo-code", "ai"],
+ "files": ["dist"]
}
diff --git a/packages/types/package.json b/packages/types/package.json
index feda97f289..44fde30b59 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -18,8 +18,9 @@
"check-types": "tsc --noEmit",
"test": "vitest run",
"build": "tsup",
- "npm:publish": "cd npm && npm version minor && cd - && tsup --outDir npm/dist && cd npm && npm publish",
- "clean": "rimraf dist npm/dist .turbo"
+ "build:watch": "tsup --watch --outDir npm/dist --onSuccess 'echo ✅ Types rebuilt to npm/dist'",
+ "npm:publish": "node scripts/publish-npm.cjs",
+ "clean": "rimraf dist .turbo"
},
"dependencies": {
"zod": "^3.25.61"
@@ -27,7 +28,8 @@
"devDependencies": {
"@roo-code/config-eslint": "workspace:^",
"@roo-code/config-typescript": "workspace:^",
- "@types/node": "20.x",
+ "@types/node": "^24.1.0",
+ "globals": "^16.3.0",
"tsup": "^8.3.5",
"vitest": "^3.2.3"
}
diff --git a/packages/types/scripts/publish-npm.cjs b/packages/types/scripts/publish-npm.cjs
new file mode 100644
index 0000000000..467ae3d506
--- /dev/null
+++ b/packages/types/scripts/publish-npm.cjs
@@ -0,0 +1,351 @@
+/* eslint-env node */
+
+const fs = require("fs")
+const path = require("path")
+const { execSync } = require("child_process")
+const readline = require("readline")
+
+const PACKAGE_NAME = "@roo-code/types"
+const BRANCH_NAME = "roo-code-types-v"
+
+const rootDir = path.join(__dirname, "..")
+const npmDir = path.join(rootDir, "npm")
+const monorepoPackagePath = path.join(rootDir, "package.json")
+const npmMetadataPath = path.join(npmDir, "package.metadata.json")
+const npmPackagePath = path.join(npmDir, "package.json")
+
+const args = process.argv.slice(2)
+const publishOnly = args.includes("--publish-only")
+
+async function confirmPublish() {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ })
+
+ return new Promise((resolve) => {
+ rl.question("\n⚠️ Are you sure you want to publish to npm? (y/n): ", (answer) => {
+ rl.close()
+ resolve(answer.toLowerCase() === "y")
+ })
+ })
+}
+
+function updatePackageVersion(filePath, version) {
+ try {
+ const packageContent = JSON.parse(fs.readFileSync(filePath, "utf8"))
+ const oldVersion = packageContent.version
+ packageContent.version = version
+ fs.writeFileSync(filePath, JSON.stringify(packageContent, null, 2) + "\n")
+
+ try {
+ execSync(`npx prettier --write "${filePath}"`, { stdio: "pipe" })
+ console.log(`✨ Formatted ${path.basename(filePath)} with prettier`)
+ } catch (prettierError) {
+ console.warn(`⚠️ Could not format with prettier:`, prettierError.message)
+ }
+
+ const fileName = path.basename(filePath)
+ console.log(`✅ Updated ${fileName} version: ${oldVersion} → ${version}`)
+ return oldVersion
+ } catch (error) {
+ throw new Error(`Failed to update version in ${path.basename(filePath)}: ${error.message}`)
+ }
+}
+
+function syncVersionToMetadata(version) {
+ console.log(" 📝 Syncing version to package.metadata.json...")
+ updatePackageVersion(npmMetadataPath, version)
+}
+
+function commitVersionChanges(version) {
+ try {
+ console.log(" 📝 Committing version changes to git...")
+
+ try {
+ const status = execSync("git status --porcelain", { encoding: "utf8" })
+ const relevantChanges = status.split("\n").filter((line) => line.includes("packages/types/npm/package"))
+
+ if (relevantChanges.length === 0) {
+ console.log(" ⚠️ No version changes to commit")
+ return
+ }
+ } catch (error) {
+ console.warn(" ⚠️ Could not check git status:", error.message)
+ }
+
+ execSync("git add .", { stdio: "pipe" })
+ const commitMessage = `chore: bump version to v${version}`
+ execSync(`git commit -m "${commitMessage}"`, { stdio: "pipe" })
+ console.log(` ✅ Committed: ${commitMessage}`)
+ } catch (error) {
+ console.warn(" ⚠️ Could not commit version changes:", error.message)
+ console.log(" You may need to commit these changes manually.")
+ }
+}
+
+function checkGitHubCLI() {
+ try {
+ execSync("gh --version", { stdio: "pipe" })
+ execSync("gh auth status", { stdio: "pipe" })
+ return true
+ } catch (_error) {
+ return false
+ }
+}
+
+function createPullRequest(branchName, baseBranch, version) {
+ try {
+ console.log(` 🔄 Creating pull request...`)
+
+ if (!checkGitHubCLI()) {
+ console.warn(" ⚠️ GitHub CLI not found or not authenticated")
+ console.log(" Install gh CLI and run: gh auth login")
+ console.log(" Then manually create PR with: gh pr create")
+ return
+ }
+
+ const title = `Release: v${version}`
+ const body = `## 🚀 Release v${version}
+
+This PR contains the version bump for the SDK release v${version}.
+
+### Changes
+- Bumped version from previous to v${version}
+- Published to npm as ${PACKAGE_NAME}@${version}
+
+### Checklist
+- [x] Version bumped
+- [x] Package published to npm
+- [ ] Changelog updated (if applicable)
+- [ ] Documentation updated (if applicable)
+
+---
+*This PR was automatically created by the npm publish script.*`
+
+ try {
+ // Create the pull request
+ const prUrl = execSync(
+ `gh pr create --base "${baseBranch}" --head "${branchName}" --title "${title}" --body "${body}"`,
+ { encoding: "utf8", stdio: "pipe" },
+ ).trim()
+
+ console.log(` ✅ Pull request created: ${prUrl}`)
+ } catch (error) {
+ if (error.message.includes("already exists")) {
+ console.log(" ℹ️ Pull request already exists for this branch")
+ } else {
+ throw error
+ }
+ }
+ } catch (error) {
+ console.error(" ❌ Failed to create pull request:", error.message)
+ console.log(" You can manually create a PR with:")
+ console.log(` gh pr create --base "${baseBranch}" --head "${branchName}"`)
+ }
+}
+
+function createVersionBranchAndCommit(version) {
+ try {
+ const branchName = `${BRANCH_NAME}${version}`
+ console.log(` 🌿 Creating version branch: ${branchName}...`)
+
+ let currentBranch
+
+ try {
+ currentBranch = execSync("git rev-parse --abbrev-ref HEAD", {
+ encoding: "utf8",
+ }).trim()
+ } catch (_error) {
+ console.warn(" ⚠️ Could not determine current branch")
+ currentBranch = "main"
+ }
+
+ execSync(`git checkout -b ${branchName}`, { stdio: "pipe" })
+ console.log(` ✅ Created branch: ${branchName}`)
+ commitVersionChanges(version)
+ execSync(`git push --set-upstream origin ${branchName}`, { stdio: "pipe" })
+ console.log(` ✅ Pushed branch to origin with upstream tracking`)
+ createPullRequest(branchName, currentBranch, version)
+
+ if (currentBranch) {
+ execSync(`git checkout ${currentBranch}`, { stdio: "pipe" })
+ console.log(` ✅ Returned to branch: ${currentBranch}`)
+ }
+
+ console.log(` 🎯 Version branch created with commits: ${branchName}`)
+ } catch (error) {
+ console.error(" ❌ Failed to create version branch:", error.message)
+ console.log(" You may need to create the branch manually.")
+ }
+}
+
+function generateNpmPackage() {
+ try {
+ console.log(" 📖 Reading monorepo package.json...")
+
+ const monorepoPackageContent = fs.readFileSync(monorepoPackagePath, "utf8")
+ const monorepoPackage = JSON.parse(monorepoPackageContent)
+
+ console.log(" 📖 Reading npm package metadata...")
+
+ const npmMetadataContent = fs.readFileSync(npmMetadataPath, "utf8")
+ const npmMetadata = JSON.parse(npmMetadataContent)
+
+ console.log(" 🔨 Generating npm package.json...")
+
+ const npmPackage = {
+ ...npmMetadata,
+ dependencies: monorepoPackage.dependencies || {},
+ main: "./dist/index.cjs",
+ module: "./dist/index.js",
+ types: "./dist/index.d.ts",
+ exports: {
+ ".": {
+ types: "./dist/index.d.ts",
+ import: "./dist/index.js",
+ require: {
+ types: "./dist/index.d.cts",
+ default: "./dist/index.cjs",
+ },
+ },
+ },
+ files: ["dist"],
+ }
+
+ const outputContent = JSON.stringify(npmPackage, null, 2) + "\n"
+ fs.writeFileSync(npmPackagePath, outputContent)
+
+ console.log(" ✅ npm/package.json generated successfully")
+ console.log(` 📦 Package name: ${npmPackage.name}`)
+ console.log(` 📌 Version: ${npmPackage.version}`)
+ console.log(` 📚 Dependencies: ${Object.keys(npmPackage.dependencies).length}`)
+ } catch (error) {
+ throw new Error(`Failed to generate npm package.json: ${error.message}`)
+ }
+}
+
+async function publish() {
+ try {
+ console.log("\n🚀 NPM PUBLISH WORKFLOW")
+ if (publishOnly) {
+ console.log("📌 Mode: Publish only (no git operations)")
+ }
+ console.log("=".repeat(60))
+
+ console.log("\n📦 Step 1: Generating npm package.json...")
+ generateNpmPackage()
+
+ const npmPackage = JSON.parse(fs.readFileSync(npmPackagePath, "utf8"))
+ const originalVersion = npmPackage.version // Save original version
+ console.log(`\n📌 Current version: ${npmPackage.version}`)
+ console.log(`📦 Package name: ${npmPackage.name}`)
+
+ console.log("\n📈 Step 2: Bumping version (minor)...")
+
+ try {
+ execSync("npm version minor --no-git-tag-version", {
+ cwd: npmDir,
+ stdio: "inherit",
+ })
+ } catch (error) {
+ console.error("❌ Failed to bump version:", error.message)
+ throw error
+ }
+
+ const updatedPackage = JSON.parse(fs.readFileSync(npmPackagePath, "utf8"))
+ console.log(`✅ New version: ${updatedPackage.version}`)
+
+ console.log("\n🔨 Step 3: Building production bundle...")
+ console.log(" This may take a moment...")
+
+ try {
+ execSync("NODE_ENV=production pnpm tsup --outDir npm/dist", {
+ cwd: rootDir,
+ stdio: "inherit",
+ })
+
+ console.log("✅ Production build complete")
+ } catch (error) {
+ console.error("❌ Build failed:", error.message)
+ throw error
+ }
+
+ console.log("\n" + "=".repeat(60))
+ console.log("📋 PUBLISH SUMMARY:")
+ console.log(` Package: ${updatedPackage.name}`)
+ console.log(` Version: ${updatedPackage.version}`)
+ console.log(` Registry: ${updatedPackage.publishConfig?.registry || "https://registry.npmjs.org/"}`)
+ console.log(` Access: ${updatedPackage.publishConfig?.access || "public"}`)
+ console.log("=".repeat(60))
+
+ const confirmed = await confirmPublish()
+
+ if (!confirmed) {
+ console.log("\n❌ Publishing cancelled by user")
+ console.log("🔙 Reverting version change...")
+
+ try {
+ updatePackageVersion(npmPackagePath, originalVersion)
+ } catch (revertError) {
+ console.error("⚠️ Could not revert version:", revertError.message)
+ console.log(` You may need to manually change version back to ${originalVersion}`)
+ }
+
+ process.exit(0)
+ }
+
+ console.log("\n💾 Step 4: Syncing version to metadata...")
+ syncVersionToMetadata(updatedPackage.version)
+
+ console.log("\n🚀 Step 5: Publishing to npm...")
+
+ try {
+ execSync("npm publish", {
+ cwd: npmDir,
+ stdio: "inherit",
+ })
+ } catch (error) {
+ console.error("❌ Publish failed:", error.message)
+ console.error("💡 The package was built but not published.")
+ console.error(" You can try publishing manually from the npm directory.")
+
+ throw error
+ }
+
+ if (!publishOnly) {
+ console.log("\n🌿 Step 6: Creating version branch, committing, and opening PR...")
+ createVersionBranchAndCommit(updatedPackage.version)
+ } else {
+ console.log("\n📝 Step 6: Skipping version branch creation (--publish-only mode)")
+ }
+
+ console.log("\n" + "=".repeat(60))
+ console.log("✅ Successfully published to npm!")
+ console.log(`🎉 ${updatedPackage.name}@${updatedPackage.version} is now live`)
+ console.log(`📦 View at: https://www.npmjs.com/package/${updatedPackage.name}`)
+
+ if (!publishOnly) {
+ console.log(`🌿 Version branch: ${BRANCH_NAME}${updatedPackage.version}`)
+ }
+
+ console.log("=".repeat(60) + "\n")
+ } catch (error) {
+ console.error("\n❌ Error during publish process:", error.message)
+ console.error("\n💡 Troubleshooting tips:")
+ console.error(" 1. Ensure you are logged in to npm: npm whoami")
+ console.error(" 2. Check your npm permissions for this package")
+ console.error(" 3. Verify the package name is not already taken")
+ console.error(" 4. Make sure all dependencies are installed: pnpm install")
+ process.exit(1)
+ }
+}
+
+async function main() {
+ await publish()
+}
+
+main().catch((error) => {
+ console.error("Unexpected error:", error)
+ process.exit(1)
+})
diff --git a/packages/types/src/cloud.ts b/packages/types/src/cloud.ts
deleted file mode 100644
index be9a039d43..0000000000
--- a/packages/types/src/cloud.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { z } from "zod"
-
-import { globalSettingsSchema } from "./global-settings.js"
-import { mcpMarketplaceItemSchema } from "./marketplace.js"
-import { discriminatedProviderSettingsWithIdSchema } from "./provider-settings.js"
-
-/**
- * CloudUserInfo
- */
-
-export interface CloudUserInfo {
- id?: string
- name?: string
- email?: string
- picture?: string
- organizationId?: string
- organizationName?: string
- organizationRole?: string
- organizationImageUrl?: string
- extensionBridgeEnabled?: boolean
-}
-
-/**
- * CloudOrganization Types
- */
-
-export interface CloudOrganization {
- id: string
- name: string
- slug?: string
- image_url?: string
- has_image?: boolean
- created_at?: number
- updated_at?: number
-}
-
-export interface CloudOrganizationMembership {
- id: string
- organization: CloudOrganization
- role: string
- permissions?: string[]
- created_at?: number
- updated_at?: number
-}
-
-/**
- * OrganizationAllowList
- */
-
-export const organizationAllowListSchema = z.object({
- allowAll: z.boolean(),
- providers: z.record(
- z.object({
- allowAll: z.boolean(),
- models: z.array(z.string()).optional(),
- }),
- ),
-})
-
-export type OrganizationAllowList = z.infer
-
-/**
- * OrganizationDefaultSettings
- */
-
-export const organizationDefaultSettingsSchema = globalSettingsSchema
- .pick({
- enableCheckpoints: true,
- fuzzyMatchThreshold: true,
- maxOpenTabsContext: true,
- maxReadFileLine: true,
- maxWorkspaceFiles: true,
- showRooIgnoredFiles: true,
- terminalCommandDelay: true,
- terminalCompressProgressBar: true,
- terminalOutputLineLimit: true,
- terminalShellIntegrationDisabled: true,
- terminalShellIntegrationTimeout: true,
- terminalZshClearEolMark: true,
- })
- // Add stronger validations for some fields.
- .merge(
- z.object({
- maxOpenTabsContext: z.number().int().nonnegative().optional(),
- maxReadFileLine: z.number().int().gte(-1).optional(),
- maxWorkspaceFiles: z.number().int().nonnegative().optional(),
- terminalCommandDelay: z.number().int().nonnegative().optional(),
- terminalOutputLineLimit: z.number().int().nonnegative().optional(),
- terminalShellIntegrationTimeout: z.number().int().nonnegative().optional(),
- }),
- )
-
-export type OrganizationDefaultSettings = z.infer
-
-/**
- * OrganizationCloudSettings
- */
-
-export const organizationCloudSettingsSchema = z.object({
- recordTaskMessages: z.boolean().optional(),
- enableTaskSharing: z.boolean().optional(),
- taskShareExpirationDays: z.number().int().positive().optional(),
- allowMembersViewAllTasks: z.boolean().optional(),
-})
-
-export type OrganizationCloudSettings = z.infer
-
-/**
- * Organization Settings
- */
-
-export const organizationSettingsSchema = z.object({
- version: z.number(),
- cloudSettings: organizationCloudSettingsSchema.optional(),
- defaultSettings: organizationDefaultSettingsSchema,
- allowList: organizationAllowListSchema,
- hiddenMcps: z.array(z.string()).optional(),
- hideMarketplaceMcps: z.boolean().optional(),
- mcps: z.array(mcpMarketplaceItemSchema).optional(),
- providerProfiles: z.record(z.string(), discriminatedProviderSettingsWithIdSchema).optional(),
-})
-
-export type OrganizationSettings = z.infer
-
-/**
- * Constants
- */
-
-export const ORGANIZATION_ALLOW_ALL: OrganizationAllowList = {
- allowAll: true,
- providers: {},
-} as const
-
-export const ORGANIZATION_DEFAULT: OrganizationSettings = {
- version: 0,
- cloudSettings: {
- recordTaskMessages: true,
- enableTaskSharing: true,
- taskShareExpirationDays: 30,
- allowMembersViewAllTasks: true,
- },
- defaultSettings: {},
- allowList: ORGANIZATION_ALLOW_ALL,
-} as const
-
-/**
- * Share Types
- */
-
-export const shareResponseSchema = z.object({
- success: z.boolean(),
- shareUrl: z.string().optional(),
- error: z.string().optional(),
- isNewShare: z.boolean().optional(),
- manageUrl: z.string().optional(),
-})
-
-export type ShareResponse = z.infer
diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts
index f5e9fc32bd..c071726d8a 100644
--- a/packages/types/src/global-settings.ts
+++ b/packages/types/src/global-settings.ts
@@ -194,6 +194,7 @@ export const SECRET_STATE_KEYS = [
"huggingFaceApiKey",
"sambaNovaApiKey",
"fireworksApiKey",
+ "ioIntelligenceApiKey",
] as const satisfies readonly (keyof ProviderSettings)[]
export type SecretState = Pick
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index dcbb1c4f54..c65d070086 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -1,5 +1,4 @@
export * from "./api.js"
-export * from "./cloud.js"
export * from "./codebase-index.js"
export * from "./events.js"
export * from "./experiment.js"
@@ -13,7 +12,6 @@ export * from "./message.js"
export * from "./mode.js"
export * from "./model.js"
export * from "./provider-settings.js"
-export * from "./sharing.js"
export * from "./task.js"
export * from "./todo.js"
export * from "./telemetry.js"
diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts
index 21baf3f203..7197ab29a1 100644
--- a/packages/types/src/message.ts
+++ b/packages/types/src/message.ts
@@ -176,6 +176,17 @@ export const clineMessageSchema = z.object({
contextCondense: contextCondenseSchema.optional(),
isProtected: z.boolean().optional(),
apiProtocol: z.union([z.literal("openai"), z.literal("anthropic")]).optional(),
+ metadata: z
+ .object({
+ gpt5: z
+ .object({
+ previous_response_id: z.string().optional(),
+ instructions: z.string().optional(),
+ reasoning_summary: z.string().optional(),
+ })
+ .optional(),
+ })
+ .optional(),
})
export type ClineMessage = z.infer
diff --git a/packages/types/src/model.ts b/packages/types/src/model.ts
index a09790578b..90b61ad879 100644
--- a/packages/types/src/model.ts
+++ b/packages/types/src/model.ts
@@ -44,6 +44,8 @@ export const modelInfoSchema = z.object({
supportsImages: z.boolean().optional(),
supportsComputerUse: z.boolean().optional(),
supportsPromptCache: z.boolean(),
+ // Capability flag to indicate whether the model supports an output verbosity parameter
+ supportsVerbosity: z.boolean().optional(),
supportsReasoningBudget: z.boolean().optional(),
requiredReasoningBudget: z.boolean().optional(),
supportsReasoningEffort: z.boolean().optional(),
diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts
index f0c90101fc..fef7d811a4 100644
--- a/packages/types/src/provider-settings.ts
+++ b/packages/types/src/provider-settings.ts
@@ -3,6 +3,14 @@ import { z } from "zod"
import { reasoningEffortsSchema, verbosityLevelsSchema, modelInfoSchema } from "./model.js"
import { codebaseIndexProviderSchema } from "./codebase-index.js"
+// Bedrock Claude Sonnet 4 model ID that supports 1M context
+export const BEDROCK_CLAUDE_SONNET_4_MODEL_ID = "anthropic.claude-sonnet-4-20250514-v1:0"
+
+// Extended schema that includes "minimal" for GPT-5 models
+export const extendedReasoningEffortsSchema = z.union([reasoningEffortsSchema, z.literal("minimal")])
+
+export type ReasoningEffortWithMinimal = z.infer
+
/**
* ProviderName
*/
@@ -38,6 +46,7 @@ export const providerNames = [
"sambanova",
"zai",
"fireworks",
+ "io-intelligence",
] as const
export const providerNamesSchema = z.enum(providerNames)
@@ -76,7 +85,7 @@ const baseProviderSettingsSchema = z.object({
// Model reasoning.
enableReasoningEffort: z.boolean().optional(),
- reasoningEffort: reasoningEffortsSchema.optional(),
+ reasoningEffort: extendedReasoningEffortsSchema.optional(),
modelMaxTokens: z.number().optional(),
modelMaxThinkingTokens: z.number().optional(),
@@ -93,6 +102,7 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
apiKey: z.string().optional(),
anthropicBaseUrl: z.string().optional(),
anthropicUseAuthToken: z.boolean().optional(),
+ anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window
})
const claudeCodeSchema = apiModelIdProviderModelSchema.extend({
@@ -128,6 +138,7 @@ const bedrockSchema = apiModelIdProviderModelSchema.extend({
awsModelContextWindow: z.number().optional(),
awsBedrockEndpointEnabled: z.boolean().optional(),
awsBedrockEndpoint: z.string().optional(),
+ awsBedrock1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window
})
const vertexSchema = apiModelIdProviderModelSchema.extend({
@@ -219,6 +230,7 @@ const unboundSchema = baseProviderSettingsSchema.extend({
})
const requestySchema = baseProviderSettingsSchema.extend({
+ requestyBaseUrl: z.string().optional(),
requestyApiKey: z.string().optional(),
requestyModelId: z.string().optional(),
})
@@ -271,6 +283,11 @@ const fireworksSchema = apiModelIdProviderModelSchema.extend({
fireworksApiKey: z.string().optional(),
})
+const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({
+ ioIntelligenceModelId: z.string().optional(),
+ ioIntelligenceApiKey: z.string().optional(),
+})
+
const defaultSchema = z.object({
apiProvider: z.undefined(),
})
@@ -306,6 +323,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })),
zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })),
fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })),
+ ioIntelligenceSchema.merge(z.object({ apiProvider: z.literal("io-intelligence") })),
defaultSchema,
])
@@ -341,6 +359,7 @@ export const providerSettingsSchema = z.object({
...sambaNovaSchema.shape,
...zaiSchema.shape,
...fireworksSchema.shape,
+ ...ioIntelligenceSchema.shape,
...codebaseIndexProviderSchema.shape,
})
@@ -366,6 +385,7 @@ export const MODEL_ID_KEYS: Partial[] = [
"requestyModelId",
"litellmModelId",
"huggingFaceModelId",
+ "ioIntelligenceModelId",
]
export const getModelId = (settings: ProviderSettings): string | undefined => {
diff --git a/packages/types/src/providers/anthropic.ts b/packages/types/src/providers/anthropic.ts
index 0c3323cffe..2cb38537a4 100644
--- a/packages/types/src/providers/anthropic.ts
+++ b/packages/types/src/providers/anthropic.ts
@@ -8,15 +8,25 @@ export const anthropicDefaultModelId: AnthropicModelId = "claude-sonnet-4-202505
export const anthropicModels = {
"claude-sonnet-4-20250514": {
maxTokens: 64_000, // Overridden to 8k if `enableReasoningEffort` is false.
- contextWindow: 200_000,
+ contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07'
supportsImages: true,
supportsComputerUse: true,
supportsPromptCache: true,
- inputPrice: 3.0, // $3 per million input tokens
- outputPrice: 15.0, // $15 per million output tokens
+ inputPrice: 3.0, // $3 per million input tokens (≤200K context)
+ outputPrice: 15.0, // $15 per million output tokens (≤200K context)
cacheWritesPrice: 3.75, // $3.75 per million tokens
cacheReadsPrice: 0.3, // $0.30 per million tokens
supportsReasoningBudget: true,
+ // Tiered pricing for extended context (requires beta flag 'context-1m-2025-08-07')
+ tiers: [
+ {
+ contextWindow: 1_000_000, // 1M tokens with beta flag
+ inputPrice: 6.0, // $6 per million input tokens (>200K context)
+ outputPrice: 22.5, // $22.50 per million output tokens (>200K context)
+ cacheWritesPrice: 7.5, // $7.50 per million tokens (>200K context)
+ cacheReadsPrice: 0.6, // $0.60 per million tokens (>200K context)
+ },
+ ],
},
"claude-opus-4-1-20250805": {
maxTokens: 8192,
diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts
index 3d3aa8b984..d7319c132e 100644
--- a/packages/types/src/providers/bedrock.ts
+++ b/packages/types/src/providers/bedrock.ts
@@ -221,6 +221,26 @@ export const bedrockModels = {
inputPrice: 1.35,
outputPrice: 5.4,
},
+ "openai.gpt-oss-20b-1:0": {
+ maxTokens: 8192,
+ contextWindow: 128_000,
+ supportsImages: false,
+ supportsComputerUse: false,
+ supportsPromptCache: false,
+ inputPrice: 0.5,
+ outputPrice: 1.5,
+ description: "GPT-OSS 20B - Optimized for low latency and local/specialized use cases",
+ },
+ "openai.gpt-oss-120b-1:0": {
+ maxTokens: 8192,
+ contextWindow: 128_000,
+ supportsImages: false,
+ supportsComputerUse: false,
+ supportsPromptCache: false,
+ inputPrice: 2.0,
+ outputPrice: 6.0,
+ description: "GPT-OSS 120B - Production-ready, general-purpose, high-reasoning model",
+ },
"meta.llama3-3-70b-instruct-v1:0": {
maxTokens: 8192,
contextWindow: 128_000,
diff --git a/packages/types/src/providers/chutes.ts b/packages/types/src/providers/chutes.ts
index 95946122a3..539d251f2a 100644
--- a/packages/types/src/providers/chutes.ts
+++ b/packages/types/src/providers/chutes.ts
@@ -23,10 +23,12 @@ export type ChutesModelId =
| "Qwen/Qwen3-30B-A3B"
| "Qwen/Qwen3-14B"
| "Qwen/Qwen3-8B"
+ | "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8"
| "microsoft/MAI-DS-R1-FP8"
| "tngtech/DeepSeek-R1T-Chimera"
| "zai-org/GLM-4.5-Air"
| "zai-org/GLM-4.5-FP8"
+ | "moonshotai/Kimi-K2-Instruct-75k"
export const chutesDefaultModelId: ChutesModelId = "deepseek-ai/DeepSeek-R1-0528"
@@ -258,4 +260,22 @@ export const chutesModels = {
description:
"GLM-4.5-FP8 model with 128k token context window, optimized for agent-based applications with MoE architecture.",
},
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8": {
+ maxTokens: 32768,
+ contextWindow: 262144,
+ supportsImages: false,
+ supportsPromptCache: false,
+ inputPrice: 0,
+ outputPrice: 0,
+ description: "Qwen3 Coder 480B A35B Instruct FP8 model, optimized for coding tasks.",
+ },
+ "moonshotai/Kimi-K2-Instruct-75k": {
+ maxTokens: 32768,
+ contextWindow: 75000,
+ supportsImages: false,
+ supportsPromptCache: false,
+ inputPrice: 0.1481,
+ outputPrice: 0.5926,
+ description: "Moonshot AI Kimi K2 Instruct model with 75k context window.",
+ },
} as const satisfies Record
diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts
index 0ab27ea3dc..b7f1cd334e 100644
--- a/packages/types/src/providers/index.ts
+++ b/packages/types/src/providers/index.ts
@@ -8,6 +8,7 @@ export * from "./gemini.js"
export * from "./glama.js"
export * from "./groq.js"
export * from "./huggingface.js"
+export * from "./io-intelligence.js"
export * from "./lite-llm.js"
export * from "./lm-studio.js"
export * from "./mistral.js"
diff --git a/packages/types/src/providers/io-intelligence.ts b/packages/types/src/providers/io-intelligence.ts
new file mode 100644
index 0000000000..a9b845393f
--- /dev/null
+++ b/packages/types/src/providers/io-intelligence.ts
@@ -0,0 +1,44 @@
+import type { ModelInfo } from "../model.js"
+
+export type IOIntelligenceModelId =
+ | "deepseek-ai/DeepSeek-R1-0528"
+ | "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
+ | "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar"
+ | "openai/gpt-oss-120b"
+
+export const ioIntelligenceDefaultModelId: IOIntelligenceModelId = "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
+
+export const ioIntelligenceDefaultBaseUrl = "https://api.intelligence.io.solutions/api/v1"
+
+export const IO_INTELLIGENCE_CACHE_DURATION = 1000 * 60 * 60 // 1 hour
+
+export const ioIntelligenceModels = {
+ "deepseek-ai/DeepSeek-R1-0528": {
+ maxTokens: 8192,
+ contextWindow: 128000,
+ supportsImages: false,
+ supportsPromptCache: false,
+ description: "DeepSeek R1 reasoning model",
+ },
+ "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": {
+ maxTokens: 8192,
+ contextWindow: 430000,
+ supportsImages: true,
+ supportsPromptCache: false,
+ description: "Llama 4 Maverick 17B model",
+ },
+ "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar": {
+ maxTokens: 8192,
+ contextWindow: 106000,
+ supportsImages: false,
+ supportsPromptCache: false,
+ description: "Qwen3 Coder 480B specialized for coding",
+ },
+ "openai/gpt-oss-120b": {
+ maxTokens: 8192,
+ contextWindow: 131072,
+ supportsImages: false,
+ supportsPromptCache: false,
+ description: "OpenAI GPT-OSS 120B model",
+ },
+} as const satisfies Record
diff --git a/packages/types/src/providers/openai.ts b/packages/types/src/providers/openai.ts
index b319be2a5f..ff79824984 100644
--- a/packages/types/src/providers/openai.ts
+++ b/packages/types/src/providers/openai.ts
@@ -12,10 +12,13 @@ export const openAiNativeModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningEffort: true,
+ reasoningEffort: "medium",
inputPrice: 1.25,
outputPrice: 10.0,
cacheReadsPrice: 0.13,
description: "GPT-5: The best model for coding and agentic tasks across domains",
+ // supportsVerbosity is a new capability; ensure ModelInfo includes it
+ supportsVerbosity: true,
},
"gpt-5-mini-2025-08-07": {
maxTokens: 128000,
@@ -23,10 +26,12 @@ export const openAiNativeModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningEffort: true,
+ reasoningEffort: "medium",
inputPrice: 0.25,
outputPrice: 2.0,
cacheReadsPrice: 0.03,
description: "GPT-5 Mini: A faster, more cost-efficient version of GPT-5 for well-defined tasks",
+ supportsVerbosity: true,
},
"gpt-5-nano-2025-08-07": {
maxTokens: 128000,
@@ -34,10 +39,12 @@ export const openAiNativeModels = {
supportsImages: true,
supportsPromptCache: true,
supportsReasoningEffort: true,
+ reasoningEffort: "medium",
inputPrice: 0.05,
outputPrice: 0.4,
cacheReadsPrice: 0.01,
description: "GPT-5 Nano: Fastest, most cost-efficient version of GPT-5",
+ supportsVerbosity: true,
},
"gpt-4.1": {
maxTokens: 32_768,
@@ -186,15 +193,6 @@ export const openAiNativeModels = {
outputPrice: 4.4,
cacheReadsPrice: 0.55,
},
- "gpt-4.5-preview": {
- maxTokens: 16_384,
- contextWindow: 128_000,
- supportsImages: true,
- supportsPromptCache: true,
- inputPrice: 75,
- outputPrice: 150,
- cacheReadsPrice: 37.5,
- },
"gpt-4o": {
maxTokens: 16_384,
contextWindow: 128_000,
@@ -213,6 +211,17 @@ export const openAiNativeModels = {
outputPrice: 0.6,
cacheReadsPrice: 0.075,
},
+ "codex-mini-latest": {
+ maxTokens: 16_384,
+ contextWindow: 200_000,
+ supportsImages: false,
+ supportsPromptCache: false,
+ inputPrice: 1.5,
+ outputPrice: 6,
+ cacheReadsPrice: 0,
+ description:
+ "Codex Mini: Cloud-based software engineering agent powered by codex-1, a version of o3 optimized for coding tasks. Trained with reinforcement learning to generate human-style code, adhere to instructions, and iteratively run tests.",
+ },
} as const satisfies Record
export const openAiModelInfoSaneDefaults: ModelInfo = {
@@ -229,5 +238,6 @@ export const openAiModelInfoSaneDefaults: ModelInfo = {
export const azureOpenAiDefaultApiVersion = "2024-08-01-preview"
export const OPENAI_NATIVE_DEFAULT_TEMPERATURE = 0
+export const GPT5_DEFAULT_TEMPERATURE = 1.0
export const OPENAI_AZURE_AI_INFERENCE_PATH = "/models/chat/completions"
diff --git a/packages/types/src/sharing.ts b/packages/types/src/sharing.ts
deleted file mode 100644
index f295798032..0000000000
--- a/packages/types/src/sharing.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * Types related to task sharing functionality
- */
-
-/**
- * Visibility options for sharing tasks
- */
-export type ShareVisibility = "organization" | "public"
diff --git a/packages/types/src/task.ts b/packages/types/src/task.ts
index 4da1a1f6f5..4fe74c6cd4 100644
--- a/packages/types/src/task.ts
+++ b/packages/types/src/task.ts
@@ -1,3 +1,5 @@
+import { z } from "zod"
+
import { RooCodeEventName } from "./events.js"
import { type ClineMessage, type BlockingAsk, type TokenUsage } from "./message.js"
import { type ToolUsage, type ToolName } from "./tool.js"
@@ -61,15 +63,25 @@ export type TaskProviderEvents = {
* TaskLike
*/
+export const taskMetadataSchema = z.object({
+ taskId: z.string(),
+ task: z.string().optional(),
+ images: z.array(z.string()).optional(),
+})
+
+export type TaskMetadata = z.infer
+
export interface TaskLike {
readonly taskId: string
readonly rootTask?: TaskLike
readonly blockingAsk?: BlockingAsk
+ readonly metadata: TaskMetadata
on(event: K, listener: (...args: TaskEvents[K]) => void | Promise): this
off(event: K, listener: (...args: TaskEvents[K]) => void | Promise): this
setMessageResponse(text: string, images?: string[]): void
+ submitUserMessage(text: string, images?: string[]): void
}
export type TaskEvents = {
diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json
index a66434e570..2a73ee92bb 100644
--- a/packages/types/tsconfig.json
+++ b/packages/types/tsconfig.json
@@ -4,6 +4,6 @@
"types": ["vitest/globals"],
"outDir": "dist"
},
- "include": ["src"],
+ "include": ["src", "scripts", "*.config.ts"],
"exclude": ["node_modules"]
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8a0cb09263..7bcc01f194 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -22,6 +22,15 @@ importers:
'@dotenvx/dotenvx':
specifier: ^1.34.0
version: 1.44.2
+ '@roo-code/config-typescript':
+ specifier: workspace:^
+ version: link:packages/config-typescript
+ '@types/glob':
+ specifier: ^9.0.0
+ version: 9.0.0
+ '@types/node':
+ specifier: ^24.1.0
+ version: 24.2.1
'@vscode/vsce':
specifier: 3.3.2
version: 3.3.2
@@ -31,12 +40,15 @@ importers:
eslint:
specifier: ^9.27.0
version: 9.28.0(jiti@2.4.2)
+ glob:
+ specifier: ^11.0.3
+ version: 11.0.3
husky:
specifier: ^9.1.7
version: 9.1.7
knip:
specifier: ^5.44.4
- version: 5.60.2(@types/node@22.15.29)(typescript@5.8.3)
+ version: 5.60.2(@types/node@24.2.1)(typescript@5.8.3)
lint-staged:
specifier: ^16.0.0
version: 16.1.2
@@ -55,6 +67,9 @@ importers:
rimraf:
specifier: ^6.0.1
version: 6.0.1
+ tsx:
+ specifier: ^4.19.3
+ version: 4.19.4
turbo:
specifier: ^2.5.3
version: 2.5.4
@@ -235,7 +250,7 @@ importers:
version: 4.1.6
vitest:
specifier: ^3.2.3
- version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.29)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ version: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
apps/web-roo-code:
dependencies:
@@ -327,6 +342,9 @@ importers:
autoprefixer:
specifier: ^10.4.21
version: 10.4.21(postcss@8.5.4)
+ next-sitemap:
+ specifier: ^4.2.3
+ version: 4.2.3(next@15.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
postcss:
specifier: ^8.5.4
version: 8.5.4
@@ -521,14 +539,17 @@ importers:
specifier: workspace:^
version: link:../config-typescript
'@types/node':
- specifier: 20.x
- version: 20.17.57
+ specifier: ^24.1.0
+ version: 24.2.1
+ globals:
+ specifier: ^16.3.0
+ version: 16.3.0
tsup:
specifier: ^8.3.5
version: 8.5.0(jiti@2.4.2)(postcss@8.5.4)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.8.0)
vitest:
specifier: ^3.2.3
- version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ version: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
src:
dependencies:
@@ -563,8 +584,8 @@ importers:
specifier: ^1.14.0
version: 1.14.0(typescript@5.8.3)
'@roo-code/cloud':
- specifier: ^0.5.0
- version: 0.5.0
+ specifier: ^0.14.0
+ version: 0.14.0
'@roo-code/ipc':
specifier: workspace:^
version: link:../packages/ipc
@@ -703,6 +724,9 @@ importers:
simple-git:
specifier: ^3.27.0
version: 3.27.0
+ socket.io-client:
+ specifier: ^4.8.1
+ version: 4.8.1
sound-play:
specifier: ^1.1.0
version: 1.1.0
@@ -1511,6 +1535,9 @@ packages:
'@chevrotain/utils@11.0.3':
resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
+ '@corex/deepmerge@4.0.43':
+ resolution: {integrity: sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==}
+
'@csstools/color-helpers@5.0.2':
resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==}
engines: {node: '>=18'}
@@ -1936,6 +1963,14 @@ packages:
'@ioredis/commands@1.3.0':
resolution: {integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==}
+ '@isaacs/balanced-match@4.0.1':
+ resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
+ engines: {node: 20 || >=22}
+
+ '@isaacs/brace-expansion@5.0.0':
+ resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
+ engines: {node: 20 || >=22}
+
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -2103,6 +2138,9 @@ packages:
'@neon-rs/load@0.0.4':
resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==}
+ '@next/env@13.5.11':
+ resolution: {integrity: sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==}
+
'@next/env@15.2.5':
resolution: {integrity: sha512-uWkCf9C8wKTyQjqrNk+BA7eL3LOQdhL+xlmJUf2O85RM4lbzwBwot3Sqv2QGe/RGnc3zysIf1oJdtq9S00pkmQ==}
@@ -3065,11 +3103,11 @@ packages:
cpu: [x64]
os: [win32]
- '@roo-code/cloud@0.5.0':
- resolution: {integrity: sha512-4u6Ce2Rmr5a9nxhjGUMRRWUWhZc63EmF/UJ/+Az5/1JARMOp0kHN5Pwqz2QAgfD137+TFSBKQORpiN0GXrdt2w==}
+ '@roo-code/cloud@0.14.0':
+ resolution: {integrity: sha512-EKFw7sLSJcOpXQDjCMFL4EnE7OH3RhO00joteaysNYNpNe+js6kMFrBZY61rj1tZAXV/943RWeQptG4sSAfPFQ==}
- '@roo-code/types@1.44.0':
- resolution: {integrity: sha512-3xbW4pYaCgWuHF5qOsiXpIcd281dlFTe1zboUGgcUUsB414Hu3pQI86PdgJxVGtZgxtaca0eHTQ2Sqjqq8nPlA==}
+ '@roo-code/types@1.48.0':
+ resolution: {integrity: sha512-DJkA/310sLcs4vM4YLhJhc5A1JFYmdcOt+hRQMUPq+wF02yW9rc7ZZzHNDOOUZL+kk5naUd2nBRxLiJoh6o0Ng==}
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
@@ -3414,6 +3452,9 @@ packages:
resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==}
engines: {node: '>=18.0.0'}
+ '@socket.io/component-emitter@3.1.2':
+ resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+
'@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
@@ -3798,6 +3839,10 @@ packages:
'@types/glob@8.1.0':
resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==}
+ '@types/glob@9.0.0':
+ resolution: {integrity: sha512-00UxlRaIUvYm4R4W9WYkN8/J+kV8fmOQ7okeH6YFtGWFMt3odD45tpG5yA5wnL7HE6lLgjaTW5n14ju2hl2NNA==}
+ deprecated: This is a stub types definition. glob provides its own type definitions, so you do not need this installed.
+
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
@@ -3868,14 +3913,8 @@ packages:
'@types/node@20.17.57':
resolution: {integrity: sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==}
- '@types/node@20.19.1':
- resolution: {integrity: sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==}
-
- '@types/node@20.19.9':
- resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==}
-
- '@types/node@22.15.29':
- resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==}
+ '@types/node@24.2.1':
+ resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==}
'@types/prop-types@15.7.14':
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
@@ -5014,6 +5053,15 @@ packages:
supports-color:
optional: true
+ debug@4.3.7:
+ resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
@@ -5361,6 +5409,13 @@ packages:
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
+ engine.io-client@6.6.3:
+ resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==}
+
+ engine.io-parser@5.2.3:
+ resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
+ engines: {node: '>=10.0.0'}
+
enhanced-resolve@5.18.1:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'}
@@ -5981,6 +6036,11 @@ packages:
engines: {node: 20 || >=22}
hasBin: true
+ glob@11.0.3:
+ resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==}
+ engines: {node: 20 || >=22}
+ hasBin: true
+
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
@@ -6001,6 +6061,10 @@ packages:
resolution: {integrity: sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==}
engines: {node: '>=18'}
+ globals@16.3.0:
+ resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==}
+ engines: {node: '>=18'}
+
globalthis@1.0.4:
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
engines: {node: '>= 0.4'}
@@ -6267,8 +6331,8 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
- ioredis@5.7.0:
- resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==}
+ ioredis@5.6.1:
+ resolution: {integrity: sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==}
engines: {node: '>=12.22.0'}
ip-address@9.0.5:
@@ -6552,6 +6616,10 @@ packages:
resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==}
engines: {node: 20 || >=22}
+ jackspeak@4.1.1:
+ resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
+ engines: {node: 20 || >=22}
+
jest-diff@29.7.0:
resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -7314,6 +7382,10 @@ packages:
resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==}
engines: {node: 20 || >=22}
+ minimatch@10.0.3:
+ resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==}
+ engines: {node: 20 || >=22}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -7422,6 +7494,13 @@ packages:
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
engines: {node: '>= 0.4.0'}
+ next-sitemap@4.2.3:
+ resolution: {integrity: sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==}
+ engines: {node: '>=14.18'}
+ hasBin: true
+ peerDependencies:
+ next: '*'
+
next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
@@ -8609,6 +8688,14 @@ packages:
resolution: {integrity: sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==}
engines: {node: '>= 18'}
+ socket.io-client@4.8.1:
+ resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==}
+ engines: {node: '>=10.0.0'}
+
+ socket.io-parser@4.2.4:
+ resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
+ engines: {node: '>=10.0.0'}
+
socks-proxy-agent@8.0.5:
resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
engines: {node: '>= 14'}
@@ -8648,6 +8735,7 @@ packages:
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
+ deprecated: The work that was done in this beta branch won't be included in future versions
space-separated-tokens@1.1.5:
resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==}
@@ -9222,8 +9310,8 @@ packages:
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
- undici-types@6.21.0:
- resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ undici-types@7.10.0:
+ resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
undici@6.21.3:
resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==}
@@ -9653,6 +9741,18 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ ws@8.17.1:
+ resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
ws@8.18.2:
resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==}
engines: {node: '>=10.0.0'}
@@ -9696,6 +9796,10 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+ xmlhttprequest-ssl@2.1.2:
+ resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
+ engines: {node: '>=0.4.0'}
+
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
@@ -10733,6 +10837,8 @@ snapshots:
'@chevrotain/utils@11.0.3': {}
+ '@corex/deepmerge@4.0.43': {}
+
'@csstools/color-helpers@5.0.2': {}
'@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
@@ -11090,6 +11196,12 @@ snapshots:
'@ioredis/commands@1.3.0': {}
+ '@isaacs/balanced-match@4.0.1': {}
+
+ '@isaacs/brace-expansion@5.0.0':
+ dependencies:
+ '@isaacs/balanced-match': 4.0.1
+
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -11118,7 +11230,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
- '@types/node': 20.17.57
+ '@types/node': 24.2.1
'@types/yargs': 17.0.33
chalk: 4.1.2
@@ -11324,6 +11436,8 @@ snapshots:
'@neon-rs/load@0.0.4':
optional: true
+ '@next/env@13.5.11': {}
+
'@next/env@15.2.5': {}
'@next/eslint-plugin-next@15.3.2':
@@ -12191,16 +12305,19 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.40.2':
optional: true
- '@roo-code/cloud@0.5.0':
+ '@roo-code/cloud@0.14.0':
dependencies:
- '@roo-code/types': 1.44.0
- ioredis: 5.7.0
+ '@roo-code/types': 1.48.0
+ ioredis: 5.6.1
p-wait-for: 5.0.2
+ socket.io-client: 4.8.1
zod: 3.25.76
transitivePeerDependencies:
+ - bufferutil
- supports-color
+ - utf-8-validate
- '@roo-code/types@1.44.0':
+ '@roo-code/types@1.48.0':
dependencies:
zod: 3.25.76
@@ -12727,6 +12844,8 @@ snapshots:
'@smithy/util-buffer-from': 4.0.0
tslib: 2.8.1
+ '@socket.io/component-emitter@3.1.2': {}
+
'@standard-schema/utils@0.3.0': {}
'@swc/counter@0.1.3': {}
@@ -13111,7 +13230,11 @@ snapshots:
'@types/glob@8.1.0':
dependencies:
'@types/minimatch': 5.1.2
- '@types/node': 20.17.57
+ '@types/node': 24.2.1
+
+ '@types/glob@9.0.0':
+ dependencies:
+ glob: 11.0.3
'@types/hast@3.0.4':
dependencies:
@@ -13164,12 +13287,12 @@ snapshots:
'@types/node-fetch@2.6.12':
dependencies:
- '@types/node': 20.17.57
+ '@types/node': 24.2.1
form-data: 4.0.4
'@types/node-ipc@9.2.3':
dependencies:
- '@types/node': 20.17.57
+ '@types/node': 24.2.1
'@types/node@12.20.55': {}
@@ -13187,18 +13310,9 @@ snapshots:
dependencies:
undici-types: 6.19.8
- '@types/node@20.19.1':
+ '@types/node@24.2.1':
dependencies:
- undici-types: 6.21.0
-
- '@types/node@20.19.9':
- dependencies:
- undici-types: 6.21.0
- optional: true
-
- '@types/node@22.15.29':
- dependencies:
- undici-types: 6.21.0
+ undici-types: 7.10.0
'@types/prop-types@15.7.14': {}
@@ -13229,11 +13343,11 @@ snapshots:
'@types/stream-chain@2.1.0':
dependencies:
- '@types/node': 20.19.1
+ '@types/node': 24.2.1
'@types/stream-json@1.7.8':
dependencies:
- '@types/node': 20.19.1
+ '@types/node': 24.2.1
'@types/stream-chain': 2.1.0
'@types/string-similarity@4.0.2': {}
@@ -13259,7 +13373,7 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 24.2.1
optional: true
'@types/yargs-parser@21.0.3': {}
@@ -13270,7 +13384,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
- '@types/node': 20.17.50
+ '@types/node': 24.2.1
optional: true
'@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3)':
@@ -13395,13 +13509,13 @@ snapshots:
optionalDependencies:
vite: 6.3.5(@types/node@20.17.57)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
- '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
+ '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
- vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite: 6.3.5(@types/node@24.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -13432,7 +13546,7 @@ snapshots:
sirv: 3.0.1
tinyglobby: 0.2.14
tinyrainbow: 2.0.0
- vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.29)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
'@vitest/utils@3.2.4':
dependencies:
@@ -13513,7 +13627,7 @@ snapshots:
cockatiel: 3.2.1
commander: 12.1.0
form-data: 4.0.4
- glob: 11.0.2
+ glob: 11.0.3
hosted-git-info: 4.1.0
jsonc-parser: 3.3.1
leven: 3.1.0
@@ -14519,6 +14633,10 @@ snapshots:
dependencies:
ms: 2.1.3
+ debug@4.3.7:
+ dependencies:
+ ms: 2.1.3
+
debug@4.4.1(supports-color@8.1.1):
dependencies:
ms: 2.1.3
@@ -14752,6 +14870,20 @@ snapshots:
dependencies:
once: 1.4.0
+ engine.io-client@6.6.3:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ engine.io-parser: 5.2.3
+ ws: 8.17.1
+ xmlhttprequest-ssl: 2.1.2
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ engine.io-parser@5.2.3: {}
+
enhanced-resolve@5.18.1:
dependencies:
graceful-fs: 4.2.11
@@ -15625,6 +15757,15 @@ snapshots:
package-json-from-dist: 1.0.1
path-scurry: 2.0.0
+ glob@11.0.3:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 4.1.1
+ minimatch: 10.0.3
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 2.0.0
+
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
@@ -15642,6 +15783,8 @@ snapshots:
globals@16.1.0: {}
+ globals@16.3.0: {}
+
globalthis@1.0.4:
dependencies:
define-properties: 1.2.1
@@ -15965,7 +16108,7 @@ snapshots:
internmap@2.0.3: {}
- ioredis@5.7.0:
+ ioredis@5.6.1:
dependencies:
'@ioredis/commands': 1.3.0
cluster-key-slot: 1.1.2
@@ -16231,6 +16374,10 @@ snapshots:
dependencies:
'@isaacs/cliui': 8.0.2
+ jackspeak@4.1.1:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+
jest-diff@29.7.0:
dependencies:
chalk: 4.1.2
@@ -16262,7 +16409,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
- '@types/node': 20.17.57
+ '@types/node': 24.2.1
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@@ -16422,10 +16569,10 @@ snapshots:
kind-of@6.0.3: {}
- knip@5.60.2(@types/node@22.15.29)(typescript@5.8.3):
+ knip@5.60.2(@types/node@24.2.1)(typescript@5.8.3):
dependencies:
'@nodelib/fs.walk': 1.2.8
- '@types/node': 22.15.29
+ '@types/node': 24.2.1
fast-glob: 3.3.3
formatly: 0.2.4
jiti: 2.4.2
@@ -17256,6 +17403,10 @@ snapshots:
dependencies:
brace-expansion: 4.0.1
+ minimatch@10.0.3:
+ dependencies:
+ '@isaacs/brace-expansion': 5.0.0
+
minimatch@3.1.2:
dependencies:
brace-expansion: 4.0.1
@@ -17371,6 +17522,14 @@ snapshots:
netmask@2.0.2: {}
+ next-sitemap@4.2.3(next@15.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
+ dependencies:
+ '@corex/deepmerge': 4.0.43
+ '@next/env': 13.5.11
+ fast-glob: 3.3.3
+ minimist: 1.2.8
+ next: 15.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+
next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
@@ -18478,7 +18637,7 @@ snapshots:
rimraf@6.0.1:
dependencies:
- glob: 11.0.2
+ glob: 11.0.3
package-json-from-dist: 1.0.1
robust-predicates@3.0.2: {}
@@ -18799,6 +18958,24 @@ snapshots:
smol-toml@1.3.4: {}
+ socket.io-client@4.8.1:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ engine.io-client: 6.6.3
+ socket.io-parser: 4.2.4
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ socket.io-parser@4.2.4:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+
socks-proxy-agent@8.0.5:
dependencies:
agent-base: 7.1.3
@@ -19447,7 +19624,7 @@ snapshots:
undici-types@6.19.8: {}
- undici-types@6.21.0: {}
+ undici-types@7.10.0: {}
undici@6.21.3: {}
@@ -19722,13 +19899,13 @@ snapshots:
- tsx
- yaml
- vite-node@3.2.4(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
+ vite-node@3.2.4(@types/node@24.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
dependencies:
cac: 6.7.14
debug: 4.4.1(supports-color@8.1.1)
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite: 6.3.5(@types/node@24.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -19775,7 +19952,7 @@ snapshots:
tsx: 4.19.4
yaml: 2.8.0
- vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
+ vite@6.3.5(@types/node@24.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
dependencies:
esbuild: 0.25.5
fdir: 6.4.4(picomatch@4.0.2)
@@ -19784,7 +19961,7 @@ snapshots:
rollup: 4.40.2
tinyglobby: 0.2.13
optionalDependencies:
- '@types/node': 22.15.29
+ '@types/node': 24.2.1
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.30.1
@@ -19879,11 +20056,11 @@ snapshots:
- tsx
- yaml
- vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.29)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
+ vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))
+ '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -19901,12 +20078,12 @@ snapshots:
tinyglobby: 0.2.14
tinypool: 1.1.1
tinyrainbow: 2.0.0
- vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
- vite-node: 3.2.4(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite: 6.3.5(@types/node@24.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite-node: 3.2.4(@types/node@24.2.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
- '@types/node': 22.15.29
+ '@types/node': 24.2.1
'@vitest/ui': 3.2.4(vitest@3.2.4)
jsdom: 26.1.0
transitivePeerDependencies:
@@ -20090,6 +20267,8 @@ snapshots:
wrappy@1.0.2: {}
+ ws@8.17.1: {}
+
ws@8.18.2: {}
ws@8.18.3:
@@ -20108,6 +20287,8 @@ snapshots:
xmlchars@2.2.0: {}
+ xmlhttprequest-ssl@2.1.2: {}
+
xtend@4.0.2: {}
y18n@5.0.8: {}
diff --git a/scripts/link-packages.js b/scripts/link-packages.js
deleted file mode 100755
index 60ef3be865..0000000000
--- a/scripts/link-packages.js
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/env node
-
-const { spawn, execSync } = require("child_process")
-const path = require("path")
-const fs = require("fs")
-
-// Package configuration - Add new packages here as needed.
-const config = {
- packages: [
- {
- name: "@roo-code/cloud",
- sourcePath: "../Roo-Code-Cloud/packages/sdk",
- targetPath: "src/node_modules/@roo-code/cloud",
- npmPath: "npm",
- watchCommand: "pnpm build:development:watch",
- },
- ],
-}
-
-const args = process.argv.slice(2)
-const packageName = args.find((arg) => !arg.startsWith("--"))
-const watch = !args.includes("--no-watch")
-const unlink = args.includes("--unlink")
-
-const packages = packageName ? config.packages.filter((p) => p.name === packageName) : config.packages
-
-if (!packages.length) {
- console.error(`Package '${packageName}' not found`)
- process.exit(1)
-}
-
-packages.forEach(unlink ? unlinkPackage : linkPackage)
-
-// After unlinking, restore npm packages with a single pnpm install.
-if (unlink && packages.length > 0) {
- const srcPath = path.resolve(__dirname, "..", "src")
- console.log("\nRestoring npm packages...")
-
- try {
- execSync("pnpm install", { cwd: srcPath, stdio: "inherit" })
- console.log("Successfully restored npm packages")
- } catch (error) {
- console.error(`Failed to restore packages: ${error.message}`)
- console.log("You may need to run 'pnpm install' manually in the src directory")
- }
-}
-
-if (!unlink && watch) {
- const watchers = packages.filter((pkg) => pkg.watchCommand).map(startWatch)
-
- if (watchers.length) {
- process.on("SIGINT", () => {
- console.log("\nStopping...")
- watchers.forEach((w) => w.kill())
- process.exit(0)
- })
- console.log("\nWatching for changes. Press Ctrl+C to stop.\n")
- }
-}
-
-function linkPackage(pkg) {
- const sourcePath = path.resolve(__dirname, "..", pkg.sourcePath)
- const targetPath = path.resolve(__dirname, "..", pkg.targetPath)
-
- if (!fs.existsSync(sourcePath)) {
- console.error(`Source not found: ${sourcePath}`)
- process.exit(1)
- }
-
- // Install dependencies if needed.
- if (!fs.existsSync(path.join(sourcePath, "node_modules"))) {
- console.log(`Installing dependencies for ${pkg.name}...`)
-
- try {
- execSync("pnpm install", { cwd: sourcePath, stdio: "inherit" })
- } catch (e) {
- execSync("pnpm install --no-frozen-lockfile", { cwd: sourcePath, stdio: "inherit" })
- }
- }
-
- // Create symlink.
- fs.rmSync(targetPath, { recursive: true, force: true })
- fs.mkdirSync(path.dirname(targetPath), { recursive: true })
- const linkSource = pkg.npmPath ? path.join(sourcePath, pkg.npmPath) : sourcePath
- fs.symlinkSync(linkSource, targetPath, "dir")
- console.log(`Linked ${pkg.name}`)
-}
-
-function unlinkPackage(pkg) {
- const targetPath = path.resolve(__dirname, "..", pkg.targetPath)
- if (fs.existsSync(targetPath)) {
- fs.rmSync(targetPath, { recursive: true, force: true })
- console.log(`Unlinked ${pkg.name}`)
- }
-}
-
-function startWatch(pkg) {
- console.log(`Watching ${pkg.name}...`)
- const [cmd, ...args] = pkg.watchCommand.split(" ")
- return spawn(cmd, args, {
- cwd: path.resolve(__dirname, "..", pkg.sourcePath),
- stdio: "inherit",
- shell: true,
- })
-}
diff --git a/scripts/link-packages.ts b/scripts/link-packages.ts
new file mode 100644
index 0000000000..4d7e4a7c4c
--- /dev/null
+++ b/scripts/link-packages.ts
@@ -0,0 +1,340 @@
+import { spawn, execSync, type ChildProcess } from "child_process"
+import * as path from "path"
+import * as fs from "fs"
+import { fileURLToPath } from "url"
+import { glob } from "glob"
+
+// @ts-expect-error - TS1470: We only run this script with tsx so it will never
+// compile to CJS and it's safe to ignore this tsc error.
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+
+interface PackageConfig {
+ readonly name: string
+ readonly sourcePath: string
+ readonly targetPaths: readonly string[]
+ readonly replacePath?: string
+ readonly npmPath: string
+ readonly watchCommand?: string
+ readonly watchOutput?: {
+ readonly start: string[]
+ readonly stop: string[]
+ }
+}
+
+interface Config {
+ readonly packages: readonly PackageConfig[]
+}
+
+interface WatcherResult {
+ child: ChildProcess
+}
+
+interface NpmPackage {
+ name?: string
+ version?: string
+ type: "module"
+ dependencies: Record
+ main: string
+ module: string
+ types: string
+ exports: {
+ ".": {
+ types: string
+ import: string
+ require: {
+ types: string
+ default: string
+ }
+ }
+ }
+ files: string[]
+}
+
+const config: Config = {
+ packages: [
+ {
+ name: "@roo-code/cloud",
+ sourcePath: "../Roo-Code-Cloud/packages/sdk",
+ targetPaths: ["src/node_modules/@roo-code/cloud"] as const,
+ replacePath: "node_modules/.pnpm/@roo-code+cloud*",
+ npmPath: "npm",
+ watchCommand: "pnpm build:development:watch",
+ watchOutput: {
+ start: ["CLI Building", "CLI Change detected"],
+ stop: ["DTS ⚡️ Build success"],
+ },
+ },
+ ],
+} as const
+
+const args = process.argv.slice(2)
+const packageName = args.find((arg) => !arg.startsWith("--"))
+const watchMode = !args.includes("--no-watch")
+const unlink = args.includes("--unlink")
+
+const packages: readonly PackageConfig[] = packageName
+ ? config.packages.filter((p) => p.name === packageName)
+ : config.packages
+
+if (!packages.length) {
+ console.error(`Package '${packageName}' not found`)
+ process.exit(1)
+}
+
+function pathExists(filePath: string): boolean {
+ try {
+ fs.accessSync(filePath)
+ return true
+ } catch {
+ return false
+ }
+}
+
+function copyRecursiveSync(src: string, dest: string): void {
+ const exists = pathExists(src)
+
+ if (!exists) {
+ return
+ }
+
+ const stats = fs.statSync(src)
+ const isDirectory = stats.isDirectory()
+
+ if (isDirectory) {
+ if (!pathExists(dest)) {
+ fs.mkdirSync(dest, { recursive: true })
+ }
+
+ const children = fs.readdirSync(src)
+
+ children.forEach((childItemName) => {
+ copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName))
+ })
+ } else {
+ fs.copyFileSync(src, dest)
+ }
+}
+
+function generateNpmPackageJson(sourcePath: string, npmPath: string): string {
+ const npmDir = path.join(sourcePath, npmPath)
+ const npmPackagePath = path.join(npmDir, "package.json")
+ const npmMetadataPath = path.join(npmDir, "package.metadata.json")
+ const monorepoPackagePath = path.join(sourcePath, "package.json")
+
+ if (pathExists(npmPackagePath)) {
+ return npmPackagePath
+ }
+
+ if (!pathExists(npmMetadataPath)) {
+ throw new Error(`No package.metadata.json found in ${npmDir}`)
+ }
+
+ const monorepoPackageContent = fs.readFileSync(monorepoPackagePath, "utf8")
+
+ const monorepoPackage = JSON.parse(monorepoPackageContent) as {
+ dependencies?: Record
+ }
+
+ const npmMetadataContent = fs.readFileSync(npmMetadataPath, "utf8")
+ const npmMetadata = JSON.parse(npmMetadataContent) as Partial
+
+ const npmPackage: NpmPackage = {
+ ...npmMetadata,
+ type: "module",
+ dependencies: monorepoPackage.dependencies || {},
+ main: "./dist/index.cjs",
+ module: "./dist/index.js",
+ types: "./dist/index.d.ts",
+ exports: {
+ ".": {
+ types: "./dist/index.d.ts",
+ import: "./dist/index.js",
+ require: {
+ types: "./dist/index.d.cts",
+ default: "./dist/index.cjs",
+ },
+ },
+ },
+ files: ["dist"],
+ }
+
+ fs.writeFileSync(npmPackagePath, JSON.stringify(npmPackage, null, 2) + "\n")
+
+ return npmPackagePath
+}
+
+function linkPackage(pkg: PackageConfig): void {
+ const sourcePath = path.resolve(__dirname, "..", pkg.sourcePath)
+
+ if (!pathExists(sourcePath)) {
+ console.error(`❌ Source not found: ${sourcePath}`)
+ process.exit(1)
+ }
+
+ generateNpmPackageJson(sourcePath, pkg.npmPath)
+
+ for (const currentTargetPath of pkg.targetPaths) {
+ const targetPath = path.resolve(__dirname, "..", currentTargetPath)
+
+ if (pathExists(targetPath)) {
+ fs.rmSync(targetPath, { recursive: true, force: true })
+ }
+
+ const parentDir = path.dirname(targetPath)
+ fs.mkdirSync(parentDir, { recursive: true })
+
+ const linkSource = pkg.npmPath ? path.join(sourcePath, pkg.npmPath) : sourcePath
+ copyRecursiveSync(linkSource, targetPath)
+ }
+}
+
+function unlinkPackage(pkg: PackageConfig): void {
+ for (const currentTargetPath of pkg.targetPaths) {
+ const targetPath = path.resolve(__dirname, "..", currentTargetPath)
+
+ if (pathExists(targetPath)) {
+ fs.rmSync(targetPath, { recursive: true, force: true })
+ console.log(`🗑️ Removed ${pkg.name} from ${currentTargetPath}`)
+ }
+ }
+}
+
+function startWatch(pkg: PackageConfig): WatcherResult {
+ if (!pkg.watchCommand) {
+ throw new Error(`Package ${pkg.name} has no watch command configured`)
+ }
+
+ const commandParts = pkg.watchCommand.split(" ")
+ const [cmd, ...args] = commandParts
+
+ if (!cmd) {
+ throw new Error(`Invalid watch command for ${pkg.name}`)
+ }
+
+ console.log(`Watching for changes to ${pkg.sourcePath} with ${cmd} ${args.join(" ")}`)
+
+ const child = spawn(cmd, args, {
+ cwd: path.resolve(__dirname, "..", pkg.sourcePath),
+ stdio: "pipe",
+ shell: true,
+ })
+
+ let debounceTimer: NodeJS.Timeout | null = null
+
+ const DEBOUNCE_DELAY = 500
+
+ if (child.stdout) {
+ child.stdout.on("data", (data: Buffer) => {
+ const output = data.toString()
+
+ const isStarting = pkg.watchOutput?.start.some((start) => output.includes(start))
+
+ const isDone = pkg.watchOutput?.stop.some((stop) => output.includes(stop))
+
+ if (isStarting) {
+ console.log(`🔨 Building ${pkg.name}...`)
+
+ if (debounceTimer) {
+ clearTimeout(debounceTimer)
+ debounceTimer = null
+ }
+ }
+
+ if (isDone) {
+ console.log(`✅ Built ${pkg.name}`)
+
+ if (debounceTimer) {
+ clearTimeout(debounceTimer)
+ }
+
+ debounceTimer = setTimeout(() => {
+ linkPackage(pkg)
+
+ console.log(`📋 Copied ${pkg.name} to ${pkg.targetPaths.length} paths\n`)
+
+ debounceTimer = null
+ }, DEBOUNCE_DELAY)
+ }
+ })
+ }
+
+ if (child.stderr) {
+ child.stderr.on("data", (data: Buffer) => {
+ console.log(`❌ "${data.toString()}"`)
+ })
+ }
+
+ return { child }
+}
+
+function main(): void {
+ if (unlink) {
+ packages.forEach(unlinkPackage)
+
+ console.log("\n📦 Restoring npm packages...")
+
+ try {
+ execSync("pnpm install", { cwd: __dirname, stdio: "ignore" })
+ console.log("✅ npm packages restored")
+ } catch (error) {
+ console.error(`❌ Failed to restore packages: ${error instanceof Error ? error.message : String(error)}`)
+
+ console.log(" Run 'pnpm install' manually if needed")
+ }
+ } else {
+ packages.forEach((pkg) => {
+ linkPackage(pkg)
+
+ if (pkg.replacePath) {
+ const replacePattern = path.resolve(__dirname, "..", pkg.replacePath)
+
+ try {
+ const matchedPaths = glob.sync(replacePattern)
+
+ if (matchedPaths.length > 0) {
+ matchedPaths.forEach((matchedPath: string) => {
+ if (pathExists(matchedPath)) {
+ fs.rmSync(matchedPath, { recursive: true, force: true })
+ console.log(`🗑️ Removed ${pkg.name} from ${matchedPath}`)
+ }
+ })
+ } else {
+ if (pathExists(replacePattern)) {
+ fs.rmSync(replacePattern, { recursive: true, force: true })
+ console.log(`🗑️ Removed ${pkg.name} from ${replacePattern}`)
+ }
+ }
+ } catch (error) {
+ console.error(
+ `❌ Error processing replace path: ${error instanceof Error ? error.message : String(error)}`,
+ )
+ }
+ }
+ })
+
+ if (watchMode) {
+ const packagesWithWatch = packages.filter(
+ (pkg): pkg is PackageConfig & { watchCommand: string } => pkg.watchCommand !== undefined,
+ )
+
+ const watchers = packagesWithWatch.map(startWatch)
+
+ if (watchers.length > 0) {
+ process.on("SIGINT", () => {
+ console.log("\n👋 Stopping watchers...")
+
+ watchers.forEach((w) => {
+ if (w.child) {
+ w.child.kill()
+ }
+ })
+
+ process.exit(0)
+ })
+ }
+ }
+ }
+}
+
+main()
diff --git a/src/api/index.ts b/src/api/index.ts
index 57b06f7bbd..c29c230b06 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -32,6 +32,7 @@ import {
LiteLLMHandler,
ClaudeCodeHandler,
SambaNovaHandler,
+ IOIntelligenceHandler,
DoubaoHandler,
ZAiHandler,
FireworksHandler,
@@ -44,6 +45,13 @@ export interface SingleCompletionHandler {
export interface ApiHandlerCreateMessageMetadata {
mode?: string
taskId: string
+ previousResponseId?: string
+ /**
+ * When true, the provider must NOT fall back to internal continuity state
+ * (e.g., lastResponseId) if previousResponseId is absent.
+ * Used to enforce "skip once" after a condense operation.
+ */
+ suppressPreviousResponseId?: boolean
}
export interface ApiHandler {
@@ -130,6 +138,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
return new ZAiHandler(options)
case "fireworks":
return new FireworksHandler(options)
+ case "io-intelligence":
+ return new IOIntelligenceHandler(options)
default:
apiProvider satisfies "gemini-cli" | undefined
return new AnthropicHandler(options)
diff --git a/src/api/providers/__tests__/bedrock.spec.ts b/src/api/providers/__tests__/bedrock.spec.ts
index ad0ae2bdb5..8df495ca9f 100644
--- a/src/api/providers/__tests__/bedrock.spec.ts
+++ b/src/api/providers/__tests__/bedrock.spec.ts
@@ -25,6 +25,7 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => {
import { AwsBedrockHandler } from "../bedrock"
import { ConverseStreamCommand, BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime"
+import { BEDROCK_CLAUDE_SONNET_4_MODEL_ID } from "@roo-code/types"
import type { Anthropic } from "@anthropic-ai/sdk"
@@ -564,4 +565,188 @@ describe("AwsBedrockHandler", () => {
expect(typeof model.info.supportsPromptCache).toBe("boolean")
})
})
+
+ describe("1M context beta feature", () => {
+ it("should enable 1M context window when awsBedrock1MContext is true for Claude Sonnet 4", () => {
+ const handler = new AwsBedrockHandler({
+ apiModelId: BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+ awsAccessKey: "test",
+ awsSecretKey: "test",
+ awsRegion: "us-east-1",
+ awsBedrock1MContext: true,
+ })
+
+ const model = handler.getModel()
+
+ // Should have 1M context window when enabled
+ expect(model.info.contextWindow).toBe(1_000_000)
+ })
+
+ it("should use default context window when awsBedrock1MContext is false for Claude Sonnet 4", () => {
+ const handler = new AwsBedrockHandler({
+ apiModelId: BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+ awsAccessKey: "test",
+ awsSecretKey: "test",
+ awsRegion: "us-east-1",
+ awsBedrock1MContext: false,
+ })
+
+ const model = handler.getModel()
+
+ // Should use default context window (200k)
+ expect(model.info.contextWindow).toBe(200_000)
+ })
+
+ it("should not affect context window for non-Claude Sonnet 4 models", () => {
+ const handler = new AwsBedrockHandler({
+ apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
+ awsAccessKey: "test",
+ awsSecretKey: "test",
+ awsRegion: "us-east-1",
+ awsBedrock1MContext: true,
+ })
+
+ const model = handler.getModel()
+
+ // Should use default context window for non-Sonnet 4 models
+ expect(model.info.contextWindow).toBe(200_000)
+ })
+
+ it("should include anthropic_beta parameter when 1M context is enabled", async () => {
+ const handler = new AwsBedrockHandler({
+ apiModelId: BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+ awsAccessKey: "test",
+ awsSecretKey: "test",
+ awsRegion: "us-east-1",
+ awsBedrock1MContext: true,
+ })
+
+ const messages: Anthropic.Messages.MessageParam[] = [
+ {
+ role: "user",
+ content: "Test message",
+ },
+ ]
+
+ const generator = handler.createMessage("", messages)
+ await generator.next() // Start the generator
+
+ // Verify the command was created with the right payload
+ expect(mockConverseStreamCommand).toHaveBeenCalled()
+ const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any
+
+ // Should include anthropic_beta in additionalModelRequestFields
+ expect(commandArg.additionalModelRequestFields).toBeDefined()
+ expect(commandArg.additionalModelRequestFields.anthropic_beta).toEqual(["context-1m-2025-08-07"])
+ // Should not include anthropic_version since thinking is not enabled
+ expect(commandArg.additionalModelRequestFields.anthropic_version).toBeUndefined()
+ })
+
+ it("should not include anthropic_beta parameter when 1M context is disabled", async () => {
+ const handler = new AwsBedrockHandler({
+ apiModelId: BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+ awsAccessKey: "test",
+ awsSecretKey: "test",
+ awsRegion: "us-east-1",
+ awsBedrock1MContext: false,
+ })
+
+ const messages: Anthropic.Messages.MessageParam[] = [
+ {
+ role: "user",
+ content: "Test message",
+ },
+ ]
+
+ const generator = handler.createMessage("", messages)
+ await generator.next() // Start the generator
+
+ // Verify the command was created with the right payload
+ expect(mockConverseStreamCommand).toHaveBeenCalled()
+ const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any
+
+ // Should not include anthropic_beta in additionalModelRequestFields
+ expect(commandArg.additionalModelRequestFields).toBeUndefined()
+ })
+
+ it("should not include anthropic_beta parameter for non-Claude Sonnet 4 models", async () => {
+ const handler = new AwsBedrockHandler({
+ apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
+ awsAccessKey: "test",
+ awsSecretKey: "test",
+ awsRegion: "us-east-1",
+ awsBedrock1MContext: true,
+ })
+
+ const messages: Anthropic.Messages.MessageParam[] = [
+ {
+ role: "user",
+ content: "Test message",
+ },
+ ]
+
+ const generator = handler.createMessage("", messages)
+ await generator.next() // Start the generator
+
+ // Verify the command was created with the right payload
+ expect(mockConverseStreamCommand).toHaveBeenCalled()
+ const commandArg = mockConverseStreamCommand.mock.calls[0][0] as any
+
+ // Should not include anthropic_beta for non-Sonnet 4 models
+ expect(commandArg.additionalModelRequestFields).toBeUndefined()
+ })
+
+ it("should enable 1M context window with cross-region inference for Claude Sonnet 4", () => {
+ const handler = new AwsBedrockHandler({
+ apiModelId: BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+ awsAccessKey: "test",
+ awsSecretKey: "test",
+ awsRegion: "us-east-1",
+ awsUseCrossRegionInference: true,
+ awsBedrock1MContext: true,
+ })
+
+ const model = handler.getModel()
+
+ // Should have 1M context window even with cross-region prefix
+ expect(model.info.contextWindow).toBe(1_000_000)
+ // Model ID should have cross-region prefix
+ expect(model.id).toBe(`us.${BEDROCK_CLAUDE_SONNET_4_MODEL_ID}`)
+ })
+
+ it("should include anthropic_beta parameter with cross-region inference for Claude Sonnet 4", async () => {
+ const handler = new AwsBedrockHandler({
+ apiModelId: BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+ awsAccessKey: "test",
+ awsSecretKey: "test",
+ awsRegion: "us-east-1",
+ awsUseCrossRegionInference: true,
+ awsBedrock1MContext: true,
+ })
+
+ const messages: Anthropic.Messages.MessageParam[] = [
+ {
+ role: "user",
+ content: "Test message",
+ },
+ ]
+
+ const generator = handler.createMessage("", messages)
+ await generator.next() // Start the generator
+
+ // Verify the command was created with the right payload
+ expect(mockConverseStreamCommand).toHaveBeenCalled()
+ const commandArg = mockConverseStreamCommand.mock.calls[
+ mockConverseStreamCommand.mock.calls.length - 1
+ ][0] as any
+
+ // Should include anthropic_beta in additionalModelRequestFields
+ expect(commandArg.additionalModelRequestFields).toBeDefined()
+ expect(commandArg.additionalModelRequestFields.anthropic_beta).toEqual(["context-1m-2025-08-07"])
+ // Should not include anthropic_version since thinking is not enabled
+ expect(commandArg.additionalModelRequestFields.anthropic_version).toBeUndefined()
+ // Model ID should have cross-region prefix
+ expect(commandArg.modelId).toBe(`us.${BEDROCK_CLAUDE_SONNET_4_MODEL_ID}`)
+ })
+ })
})
diff --git a/src/api/providers/__tests__/chutes.spec.ts b/src/api/providers/__tests__/chutes.spec.ts
index 911c848b12..0596a911df 100644
--- a/src/api/providers/__tests__/chutes.spec.ts
+++ b/src/api/providers/__tests__/chutes.spec.ts
@@ -231,6 +231,50 @@ describe("ChutesHandler", () => {
)
})
+ it("should return Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 model with correct configuration", () => {
+ const testModelId: ChutesModelId = "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8"
+ const handlerWithModel = new ChutesHandler({
+ apiModelId: testModelId,
+ chutesApiKey: "test-chutes-api-key",
+ })
+ const model = handlerWithModel.getModel()
+ expect(model.id).toBe(testModelId)
+ expect(model.info).toEqual(
+ expect.objectContaining({
+ maxTokens: 32768,
+ contextWindow: 262144,
+ supportsImages: false,
+ supportsPromptCache: false,
+ inputPrice: 0,
+ outputPrice: 0,
+ description: "Qwen3 Coder 480B A35B Instruct FP8 model, optimized for coding tasks.",
+ temperature: 0.5, // Default temperature for non-DeepSeek models
+ }),
+ )
+ })
+
+ it("should return moonshotai/Kimi-K2-Instruct-75k model with correct configuration", () => {
+ const testModelId: ChutesModelId = "moonshotai/Kimi-K2-Instruct-75k"
+ const handlerWithModel = new ChutesHandler({
+ apiModelId: testModelId,
+ chutesApiKey: "test-chutes-api-key",
+ })
+ const model = handlerWithModel.getModel()
+ expect(model.id).toBe(testModelId)
+ expect(model.info).toEqual(
+ expect.objectContaining({
+ maxTokens: 32768,
+ contextWindow: 75000,
+ supportsImages: false,
+ supportsPromptCache: false,
+ inputPrice: 0.1481,
+ outputPrice: 0.5926,
+ description: "Moonshot AI Kimi K2 Instruct model with 75k context window.",
+ temperature: 0.5, // Default temperature for non-DeepSeek models
+ }),
+ )
+ })
+
it("completePrompt method should return text from Chutes API", async () => {
const expectedResponse = "This is a test response from Chutes"
mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] })
diff --git a/src/api/providers/__tests__/io-intelligence.spec.ts b/src/api/providers/__tests__/io-intelligence.spec.ts
new file mode 100644
index 0000000000..56baf711cd
--- /dev/null
+++ b/src/api/providers/__tests__/io-intelligence.spec.ts
@@ -0,0 +1,303 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
+import { IOIntelligenceHandler } from "../io-intelligence"
+import type { ApiHandlerOptions } from "../../../shared/api"
+import { Anthropic } from "@anthropic-ai/sdk"
+
+const mockCreate = vi.fn()
+
+// Mock OpenAI
+vi.mock("openai", () => ({
+ default: class MockOpenAI {
+ baseURL: string
+ apiKey: string
+ chat = {
+ completions: {
+ create: vi.fn(),
+ },
+ }
+ constructor(options: any) {
+ this.baseURL = options.baseURL
+ this.apiKey = options.apiKey
+ this.chat.completions.create = mockCreate
+ }
+ },
+}))
+
+// Mock the fetcher functions
+vi.mock("../fetchers/io-intelligence", () => ({
+ getIOIntelligenceModels: vi.fn(),
+ getCachedIOIntelligenceModels: vi.fn(() => ({
+ "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": {
+ maxTokens: 8192,
+ contextWindow: 430000,
+ description: "Llama 4 Maverick 17B model",
+ supportsImages: true,
+ supportsPromptCache: false,
+ },
+ "deepseek-ai/DeepSeek-R1-0528": {
+ maxTokens: 8192,
+ contextWindow: 128000,
+ supportsImages: false,
+ supportsPromptCache: false,
+ description: "DeepSeek R1 reasoning model",
+ },
+ "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar": {
+ maxTokens: 4096,
+ contextWindow: 106000,
+ supportsImages: false,
+ supportsPromptCache: false,
+ description: "Qwen3 Coder 480B specialized for coding",
+ },
+ "openai/gpt-oss-120b": {
+ maxTokens: 8192,
+ contextWindow: 131072,
+ supportsImages: false,
+ supportsPromptCache: false,
+ description: "OpenAI GPT-OSS 120B model",
+ },
+ })),
+}))
+
+// Mock constants
+vi.mock("../constants", () => ({
+ DEFAULT_HEADERS: { "User-Agent": "roo-cline" },
+}))
+
+// Mock transform functions
+vi.mock("../../transform/openai-format", () => ({
+ convertToOpenAiMessages: vi.fn((messages) => messages),
+}))
+
+describe("IOIntelligenceHandler", () => {
+ let handler: IOIntelligenceHandler
+ let mockOptions: ApiHandlerOptions
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mockOptions = {
+ ioIntelligenceApiKey: "test-api-key",
+ apiModelId: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
+ modelTemperature: 0.7,
+ includeMaxTokens: false,
+ modelMaxTokens: undefined,
+ } as ApiHandlerOptions
+
+ mockCreate.mockImplementation(async () => ({
+ [Symbol.asyncIterator]: async function* () {
+ yield {
+ choices: [
+ {
+ delta: { content: "Test response" },
+ index: 0,
+ },
+ ],
+ usage: null,
+ }
+ yield {
+ choices: [
+ {
+ delta: {},
+ index: 0,
+ },
+ ],
+ usage: {
+ prompt_tokens: 10,
+ completion_tokens: 5,
+ total_tokens: 15,
+ },
+ }
+ },
+ }))
+ handler = new IOIntelligenceHandler(mockOptions)
+ })
+
+ afterEach(() => {
+ vi.restoreAllMocks()
+ })
+
+ it("should create OpenAI client with correct configuration", () => {
+ const ioIntelligenceApiKey = "test-io-intelligence-api-key"
+ const handler = new IOIntelligenceHandler({ ioIntelligenceApiKey })
+ // Verify that the handler was created successfully
+ expect(handler).toBeInstanceOf(IOIntelligenceHandler)
+ expect(handler["client"]).toBeDefined()
+ // Verify the client has the expected properties
+ expect(handler["client"].baseURL).toBe("https://api.intelligence.io.solutions/api/v1")
+ expect(handler["client"].apiKey).toBe(ioIntelligenceApiKey)
+ })
+
+ it("should initialize with correct configuration", () => {
+ expect(handler).toBeInstanceOf(IOIntelligenceHandler)
+ expect(handler["client"]).toBeDefined()
+ expect(handler["options"]).toEqual({
+ ...mockOptions,
+ apiKey: mockOptions.ioIntelligenceApiKey,
+ })
+ })
+
+ it("should throw error when API key is missing", () => {
+ const optionsWithoutKey = { ...mockOptions }
+ delete optionsWithoutKey.ioIntelligenceApiKey
+
+ expect(() => new IOIntelligenceHandler(optionsWithoutKey)).toThrow("IO Intelligence API key is required")
+ })
+
+ it("should handle streaming response correctly", async () => {
+ const mockStream = [
+ {
+ choices: [{ delta: { content: "Hello" } }],
+ usage: null,
+ },
+ {
+ choices: [{ delta: { content: " world" } }],
+ usage: null,
+ },
+ {
+ choices: [{ delta: {} }],
+ usage: { prompt_tokens: 10, completion_tokens: 5 },
+ },
+ ]
+
+ mockCreate.mockResolvedValue({
+ [Symbol.asyncIterator]: async function* () {
+ for (const chunk of mockStream) {
+ yield chunk
+ }
+ },
+ })
+
+ const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
+
+ const stream = handler.createMessage("System prompt", messages)
+ const results = []
+
+ for await (const chunk of stream) {
+ results.push(chunk)
+ }
+
+ expect(results).toHaveLength(3)
+ expect(results[0]).toEqual({ type: "text", text: "Hello" })
+ expect(results[1]).toEqual({ type: "text", text: " world" })
+ expect(results[2]).toEqual({
+ type: "usage",
+ inputTokens: 10,
+ outputTokens: 5,
+ })
+ })
+
+ it("completePrompt method should return text from IO Intelligence API", async () => {
+ const expectedResponse = "This is a test response from IO Intelligence"
+ mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] })
+ const result = await handler.completePrompt("test prompt")
+ expect(result).toBe(expectedResponse)
+ })
+
+ it("should handle errors in completePrompt", async () => {
+ const errorMessage = "IO Intelligence API error"
+ mockCreate.mockRejectedValueOnce(new Error(errorMessage))
+ await expect(handler.completePrompt("test prompt")).rejects.toThrow(
+ `IO Intelligence completion error: ${errorMessage}`,
+ )
+ })
+
+ it("createMessage should yield text content from stream", async () => {
+ const testContent = "This is test content from IO Intelligence stream"
+
+ mockCreate.mockImplementationOnce(() => {
+ return {
+ [Symbol.asyncIterator]: () => ({
+ next: vi
+ .fn()
+ .mockResolvedValueOnce({
+ done: false,
+ value: { choices: [{ delta: { content: testContent } }] },
+ })
+ .mockResolvedValueOnce({ done: true }),
+ }),
+ }
+ })
+
+ const stream = handler.createMessage("system prompt", [])
+ const firstChunk = await stream.next()
+
+ expect(firstChunk.done).toBe(false)
+ expect(firstChunk.value).toEqual({ type: "text", text: testContent })
+ })
+
+ it("createMessage should yield usage data from stream", async () => {
+ mockCreate.mockImplementationOnce(() => {
+ return {
+ [Symbol.asyncIterator]: () => ({
+ next: vi
+ .fn()
+ .mockResolvedValueOnce({
+ done: false,
+ value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } },
+ })
+ .mockResolvedValueOnce({ done: true }),
+ }),
+ }
+ })
+
+ const stream = handler.createMessage("system prompt", [])
+ const firstChunk = await stream.next()
+
+ expect(firstChunk.done).toBe(false)
+ expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 })
+ })
+
+ it("should return model info from cache when available", () => {
+ const model = handler.getModel()
+ expect(model.id).toBe("meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8")
+ expect(model.info).toEqual({
+ maxTokens: 8192,
+ contextWindow: 430000,
+ description: "Llama 4 Maverick 17B model",
+ supportsImages: true,
+ supportsPromptCache: false,
+ })
+ })
+
+ it("should return fallback model info when not in cache", () => {
+ const handlerWithUnknownModel = new IOIntelligenceHandler({
+ ...mockOptions,
+ apiModelId: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
+ })
+ const model = handlerWithUnknownModel.getModel()
+ expect(model.id).toBe("meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8")
+ expect(model.info).toEqual({
+ maxTokens: 8192,
+ contextWindow: 430000,
+ description: "Llama 4 Maverick 17B model",
+ supportsImages: true,
+ supportsPromptCache: false,
+ })
+ })
+
+ it("should use default model when no model is specified", () => {
+ const handlerWithoutModel = new IOIntelligenceHandler({
+ ...mockOptions,
+ apiModelId: undefined,
+ })
+ const model = handlerWithoutModel.getModel()
+ expect(model.id).toBe("meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8")
+ })
+
+ it("should handle empty response from completePrompt", async () => {
+ mockCreate.mockResolvedValueOnce({
+ choices: [{ message: { content: null } }],
+ })
+
+ const result = await handler.completePrompt("Test prompt")
+ expect(result).toBe("")
+ })
+
+ it("should handle missing choices in completePrompt response", async () => {
+ mockCreate.mockResolvedValueOnce({
+ choices: [],
+ })
+
+ const result = await handler.completePrompt("Test prompt")
+ expect(result).toBe("")
+ })
+})
diff --git a/src/api/providers/__tests__/lm-studio-timeout.spec.ts b/src/api/providers/__tests__/lm-studio-timeout.spec.ts
new file mode 100644
index 0000000000..659fcaaf67
--- /dev/null
+++ b/src/api/providers/__tests__/lm-studio-timeout.spec.ts
@@ -0,0 +1,91 @@
+// npx vitest run api/providers/__tests__/lm-studio-timeout.spec.ts
+
+import { LmStudioHandler } from "../lm-studio"
+import { ApiHandlerOptions } from "../../../shared/api"
+
+// Mock the timeout config utility
+vitest.mock("../utils/timeout-config", () => ({
+ getApiRequestTimeout: vitest.fn(),
+}))
+
+import { getApiRequestTimeout } from "../utils/timeout-config"
+
+// Mock OpenAI
+const mockOpenAIConstructor = vitest.fn()
+vitest.mock("openai", () => {
+ return {
+ __esModule: true,
+ default: vitest.fn().mockImplementation((config) => {
+ mockOpenAIConstructor(config)
+ return {
+ chat: {
+ completions: {
+ create: vitest.fn(),
+ },
+ },
+ }
+ }),
+ }
+})
+
+describe("LmStudioHandler timeout configuration", () => {
+ beforeEach(() => {
+ vitest.clearAllMocks()
+ })
+
+ it("should use default timeout of 600 seconds when no configuration is set", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(600000)
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "llama2",
+ lmStudioModelId: "llama2",
+ lmStudioBaseUrl: "http://localhost:1234",
+ }
+
+ new LmStudioHandler(options)
+
+ expect(getApiRequestTimeout).toHaveBeenCalled()
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ baseURL: "http://localhost:1234/v1",
+ apiKey: "noop",
+ timeout: 600000, // 600 seconds in milliseconds
+ }),
+ )
+ })
+
+ it("should use custom timeout when configuration is set", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(1200000) // 20 minutes
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "llama2",
+ lmStudioModelId: "llama2",
+ lmStudioBaseUrl: "http://localhost:1234",
+ }
+
+ new LmStudioHandler(options)
+
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ timeout: 1200000, // 1200 seconds in milliseconds
+ }),
+ )
+ })
+
+ it("should handle zero timeout (no timeout)", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(0)
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "llama2",
+ lmStudioModelId: "llama2",
+ }
+
+ new LmStudioHandler(options)
+
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ timeout: 0, // No timeout
+ }),
+ )
+ })
+})
diff --git a/src/api/providers/__tests__/ollama-timeout.spec.ts b/src/api/providers/__tests__/ollama-timeout.spec.ts
new file mode 100644
index 0000000000..db78f206c0
--- /dev/null
+++ b/src/api/providers/__tests__/ollama-timeout.spec.ts
@@ -0,0 +1,108 @@
+// npx vitest run api/providers/__tests__/ollama-timeout.spec.ts
+
+import { OllamaHandler } from "../ollama"
+import { ApiHandlerOptions } from "../../../shared/api"
+
+// Mock the timeout config utility
+vitest.mock("../utils/timeout-config", () => ({
+ getApiRequestTimeout: vitest.fn(),
+}))
+
+import { getApiRequestTimeout } from "../utils/timeout-config"
+
+// Mock OpenAI
+const mockOpenAIConstructor = vitest.fn()
+vitest.mock("openai", () => {
+ return {
+ __esModule: true,
+ default: vitest.fn().mockImplementation((config) => {
+ mockOpenAIConstructor(config)
+ return {
+ chat: {
+ completions: {
+ create: vitest.fn(),
+ },
+ },
+ }
+ }),
+ }
+})
+
+describe("OllamaHandler timeout configuration", () => {
+ beforeEach(() => {
+ vitest.clearAllMocks()
+ })
+
+ it("should use default timeout of 600 seconds when no configuration is set", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(600000)
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "llama2",
+ ollamaModelId: "llama2",
+ ollamaBaseUrl: "http://localhost:11434",
+ }
+
+ new OllamaHandler(options)
+
+ expect(getApiRequestTimeout).toHaveBeenCalled()
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ baseURL: "http://localhost:11434/v1",
+ apiKey: "ollama",
+ timeout: 600000, // 600 seconds in milliseconds
+ }),
+ )
+ })
+
+ it("should use custom timeout when configuration is set", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(3600000) // 1 hour
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "llama2",
+ ollamaModelId: "llama2",
+ }
+
+ new OllamaHandler(options)
+
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ timeout: 3600000, // 3600 seconds in milliseconds
+ }),
+ )
+ })
+
+ it("should handle zero timeout (no timeout)", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(0)
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "llama2",
+ ollamaModelId: "llama2",
+ ollamaBaseUrl: "http://localhost:11434",
+ }
+
+ new OllamaHandler(options)
+
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ timeout: 0, // No timeout
+ }),
+ )
+ })
+
+ it("should use default base URL when not provided", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(600000)
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "llama2",
+ ollamaModelId: "llama2",
+ }
+
+ new OllamaHandler(options)
+
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ baseURL: "http://localhost:11434/v1",
+ }),
+ )
+ })
+})
diff --git a/src/api/providers/__tests__/openai-native.spec.ts b/src/api/providers/__tests__/openai-native.spec.ts
index fdd71ba3f6..0acdb6202e 100644
--- a/src/api/providers/__tests__/openai-native.spec.ts
+++ b/src/api/providers/__tests__/openai-native.spec.ts
@@ -160,8 +160,12 @@ describe("OpenAiNativeHandler", () => {
expect(results.length).toBe(1)
expect(results[0].type).toBe("usage")
// Use type assertion to avoid TypeScript errors
- expect((results[0] as any).inputTokens).toBe(0)
- expect((results[0] as any).outputTokens).toBe(0)
+ const usageResult = results[0] as any
+ expect(usageResult.inputTokens).toBe(0)
+ expect(usageResult.outputTokens).toBe(0)
+ // When no cache tokens are present, they should be undefined
+ expect(usageResult.cacheWriteTokens).toBeUndefined()
+ expect(usageResult.cacheReadTokens).toBeUndefined()
// Verify developer role is used for system prompt with o1 model
expect(mockCreate).toHaveBeenCalledWith({
@@ -254,6 +258,68 @@ describe("OpenAiNativeHandler", () => {
})
})
+ it("should not include verbosity parameter for models that don't support it", async () => {
+ // Test with gpt-4.1 which does NOT support verbosity
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-4.1",
+ verbosity: "high", // Set verbosity but it should be ignored
+ })
+
+ const stream = handler.createMessage(systemPrompt, messages)
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ // Verify that verbosity is NOT included in the request
+ const callArgs = mockCreate.mock.calls[0][0]
+ expect(callArgs).not.toHaveProperty("verbosity")
+ expect(callArgs.model).toBe("gpt-4.1")
+ expect(callArgs.temperature).toBe(0)
+ expect(callArgs.stream).toBe(true)
+ })
+
+ it("should not include verbosity for gpt-4o models", async () => {
+ // Test with gpt-4o which does NOT support verbosity
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-4o",
+ verbosity: "medium", // Set verbosity but it should be ignored
+ })
+
+ const stream = handler.createMessage(systemPrompt, messages)
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ // Verify that verbosity is NOT included in the request
+ const callArgs = mockCreate.mock.calls[0][0]
+ expect(callArgs).not.toHaveProperty("verbosity")
+ expect(callArgs.model).toBe("gpt-4o")
+ })
+
+ it("should not include verbosity for gpt-4.1-mini models", async () => {
+ // Test with gpt-4.1-mini which does NOT support verbosity
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-4.1-mini",
+ verbosity: "low", // Set verbosity but it should be ignored
+ })
+
+ const stream = handler.createMessage(systemPrompt, messages)
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ // Verify that verbosity is NOT included in the request
+ const callArgs = mockCreate.mock.calls[0][0]
+ expect(callArgs).not.toHaveProperty("verbosity")
+ expect(callArgs.model).toBe("gpt-4.1-mini")
+ })
+
it("should handle empty delta content", async () => {
const mockStream = [
{ choices: [{ delta: {} }], usage: null },
@@ -286,6 +352,111 @@ describe("OpenAiNativeHandler", () => {
expect((results[1] as any).outputTokens).toBe(5)
expect((results[1] as any).totalCost).toBeCloseTo(0.00006, 6)
})
+
+ it("should handle cache tokens in streaming response", async () => {
+ const mockStream = [
+ { choices: [{ delta: { content: "Hello" } }], usage: null },
+ { choices: [{ delta: { content: " cached" } }], usage: null },
+ {
+ choices: [{ delta: { content: " response" } }],
+ usage: {
+ prompt_tokens: 100,
+ completion_tokens: 10,
+ prompt_tokens_details: {
+ cached_tokens: 80,
+ audio_tokens: 0,
+ },
+ completion_tokens_details: {
+ reasoning_tokens: 0,
+ audio_tokens: 0,
+ accepted_prediction_tokens: 0,
+ rejected_prediction_tokens: 0,
+ },
+ },
+ },
+ ]
+
+ mockCreate.mockResolvedValueOnce(
+ (async function* () {
+ for (const chunk of mockStream) {
+ yield chunk
+ }
+ })(),
+ )
+
+ const generator = handler.createMessage(systemPrompt, messages)
+ const results = []
+ for await (const result of generator) {
+ results.push(result)
+ }
+
+ // Verify text responses
+ expect(results.length).toBe(4)
+ expect(results[0]).toMatchObject({ type: "text", text: "Hello" })
+ expect(results[1]).toMatchObject({ type: "text", text: " cached" })
+ expect(results[2]).toMatchObject({ type: "text", text: " response" })
+
+ // Check usage data includes cache tokens
+ expect(results[3].type).toBe("usage")
+ const usageChunk = results[3] as any
+ expect(usageChunk.inputTokens).toBe(100) // Total input tokens (includes cached)
+ expect(usageChunk.outputTokens).toBe(10)
+ expect(usageChunk.cacheReadTokens).toBe(80) // Cached tokens from prompt_tokens_details
+ expect(usageChunk.cacheWriteTokens).toBeUndefined() // No cache write tokens in standard response
+
+ // Verify cost calculation takes cache into account
+ // GPT-4.1 pricing: input $2/1M, output $8/1M, cache read $0.5/1M
+ // OpenAI's prompt_tokens includes cached tokens, so we need to calculate:
+ // - Non-cached input tokens: 100 - 80 = 20
+ // - Cost for non-cached input: (20 / 1_000_000) * 2.0
+ // - Cost for cached input: (80 / 1_000_000) * 0.5
+ // - Cost for output: (10 / 1_000_000) * 8.0
+ const nonCachedInputTokens = 100 - 80
+ const expectedNonCachedInputCost = (nonCachedInputTokens / 1_000_000) * 2.0
+ const expectedCacheReadCost = (80 / 1_000_000) * 0.5
+ const expectedOutputCost = (10 / 1_000_000) * 8.0
+ const expectedTotalCost = expectedNonCachedInputCost + expectedCacheReadCost + expectedOutputCost
+ expect(usageChunk.totalCost).toBeCloseTo(expectedTotalCost, 10)
+ })
+
+ it("should handle cache write tokens if present", async () => {
+ const mockStream = [
+ { choices: [{ delta: { content: "Test" } }], usage: null },
+ {
+ choices: [{ delta: {} }],
+ usage: {
+ prompt_tokens: 150,
+ completion_tokens: 5,
+ prompt_tokens_details: {
+ cached_tokens: 50,
+ },
+ cache_creation_input_tokens: 30, // Cache write tokens
+ },
+ },
+ ]
+
+ mockCreate.mockResolvedValueOnce(
+ (async function* () {
+ for (const chunk of mockStream) {
+ yield chunk
+ }
+ })(),
+ )
+
+ const generator = handler.createMessage(systemPrompt, messages)
+ const results = []
+ for await (const result of generator) {
+ results.push(result)
+ }
+
+ // Check usage data includes both cache read and write tokens
+ const usageChunk = results.find((r) => r.type === "usage") as any
+ expect(usageChunk).toBeDefined()
+ expect(usageChunk.inputTokens).toBe(150)
+ expect(usageChunk.outputTokens).toBe(5)
+ expect(usageChunk.cacheReadTokens).toBe(50)
+ expect(usageChunk.cacheWriteTokens).toBe(30)
+ })
})
describe("completePrompt", () => {
@@ -461,7 +632,40 @@ describe("OpenAiNativeHandler", () => {
})
describe("GPT-5 models", () => {
- it("should handle GPT-5 model with developer role", async () => {
+ it("should handle GPT-5 model with Responses API", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ // Simulate actual GPT-5 Responses API SSE stream format
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.created","response":{"id":"test","status":"in_progress"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Hello"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":" world"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.done","response":{"usage":{"prompt_tokens":10,"completion_tokens":2}}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-2025-08-07",
@@ -473,20 +677,56 @@ describe("OpenAiNativeHandler", () => {
chunks.push(chunk)
}
- // Verify developer role is used for GPT-5 with default parameters
- expect(mockCreate).toHaveBeenCalledWith(
+ // Verify Responses API is called with correct parameters
+ expect(mockFetch).toHaveBeenCalledWith(
+ "https://api.openai.com/v1/responses",
expect.objectContaining({
- model: "gpt-5-2025-08-07",
- messages: [{ role: "developer", content: expect.stringContaining(systemPrompt) }],
- stream: true,
- stream_options: { include_usage: true },
- reasoning_effort: "minimal", // Default for GPT-5
- verbosity: "medium", // Default verbosity
+ method: "POST",
+ headers: expect.objectContaining({
+ "Content-Type": "application/json",
+ Authorization: "Bearer test-api-key",
+ Accept: "text/event-stream",
+ }),
+ body: expect.any(String),
}),
)
+ const body1 = (mockFetch.mock.calls[0][1] as any).body as string
+ expect(body1).toContain('"model":"gpt-5-2025-08-07"')
+ expect(body1).toContain('"input":"Developer: You are a helpful assistant.\\n\\nUser: Hello!"')
+ expect(body1).toContain('"effort":"medium"')
+ expect(body1).toContain('"summary":"auto"')
+ expect(body1).toContain('"verbosity":"medium"')
+ expect(body1).toContain('"temperature":1')
+ expect(body1).toContain('"max_output_tokens"')
+
+ // Verify the streamed content
+ const textChunks = chunks.filter((c) => c.type === "text")
+ expect(textChunks).toHaveLength(2)
+ expect(textChunks[0].text).toBe("Hello")
+ expect(textChunks[1].text).toBe(" world")
+
+ // Clean up
+ delete (global as any).fetch
})
- it("should handle GPT-5-mini model", async () => {
+ it("should handle GPT-5-mini model with Responses API", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Response"}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-mini-2025-08-07",
@@ -498,19 +738,36 @@ describe("OpenAiNativeHandler", () => {
chunks.push(chunk)
}
- expect(mockCreate).toHaveBeenCalledWith(
+ // Verify correct model and default parameters
+ expect(mockFetch).toHaveBeenCalledWith(
+ "https://api.openai.com/v1/responses",
expect.objectContaining({
- model: "gpt-5-mini-2025-08-07",
- messages: [{ role: "developer", content: expect.stringContaining(systemPrompt) }],
- stream: true,
- stream_options: { include_usage: true },
- reasoning_effort: "minimal", // Default for GPT-5
- verbosity: "medium", // Default verbosity
+ body: expect.stringContaining('"model":"gpt-5-mini-2025-08-07"'),
}),
)
+
+ // Clean up
+ delete (global as any).fetch
})
- it("should handle GPT-5-nano model", async () => {
+ it("should handle GPT-5-nano model with Responses API", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Nano response"}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-nano-2025-08-07",
@@ -522,19 +779,36 @@ describe("OpenAiNativeHandler", () => {
chunks.push(chunk)
}
- expect(mockCreate).toHaveBeenCalledWith(
+ // Verify correct model
+ expect(mockFetch).toHaveBeenCalledWith(
+ "https://api.openai.com/v1/responses",
expect.objectContaining({
- model: "gpt-5-nano-2025-08-07",
- messages: [{ role: "developer", content: expect.stringContaining(systemPrompt) }],
- stream: true,
- stream_options: { include_usage: true },
- reasoning_effort: "minimal", // Default for GPT-5
- verbosity: "medium", // Default verbosity
+ body: expect.stringContaining('"model":"gpt-5-nano-2025-08-07"'),
}),
)
+
+ // Clean up
+ delete (global as any).fetch
})
it("should support verbosity control for GPT-5", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Low verbosity"}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-2025-08-07",
@@ -549,18 +823,77 @@ describe("OpenAiNativeHandler", () => {
}
// Verify that verbosity is passed in the request
- expect(mockCreate).toHaveBeenCalledWith(
+ expect(mockFetch).toHaveBeenCalledWith(
+ "https://api.openai.com/v1/responses",
expect.objectContaining({
- model: "gpt-5-2025-08-07",
- messages: expect.any(Array),
- stream: true,
- stream_options: { include_usage: true },
- verbosity: "low",
+ body: expect.stringContaining('"verbosity":"low"'),
}),
)
+
+ // Clean up
+ delete (global as any).fetch
})
it("should support minimal reasoning effort for GPT-5", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Minimal effort"}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-5-2025-08-07",
+ reasoningEffort: "minimal" as any, // GPT-5 supports minimal
+ })
+
+ const stream = handler.createMessage(systemPrompt, messages)
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ // With minimal reasoning effort, the model should pass it through
+ expect(mockFetch).toHaveBeenCalledWith(
+ "https://api.openai.com/v1/responses",
+ expect.objectContaining({
+ body: expect.stringContaining('"effort":"minimal"'),
+ }),
+ )
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should support low reasoning effort for GPT-5", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Low effort response"}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-2025-08-07",
@@ -573,25 +906,48 @@ describe("OpenAiNativeHandler", () => {
chunks.push(chunk)
}
- // With low reasoning effort, the model should pass it through
- expect(mockCreate).toHaveBeenCalledWith(
+ // Should use Responses API with low reasoning effort
+ expect(mockFetch).toHaveBeenCalledWith(
+ "https://api.openai.com/v1/responses",
expect.objectContaining({
- model: "gpt-5-2025-08-07",
- messages: expect.any(Array),
- stream: true,
- stream_options: { include_usage: true },
- reasoning_effort: "low",
- verbosity: "medium", // Default verbosity
+ body: expect.any(String),
}),
)
+ const body2 = (mockFetch.mock.calls[0][1] as any).body as string
+ expect(body2).toContain('"model":"gpt-5-2025-08-07"')
+ expect(body2).toContain('"effort":"low"')
+ expect(body2).toContain('"summary":"auto"')
+ expect(body2).toContain('"verbosity":"medium"')
+ expect(body2).toContain('"temperature":1')
+ expect(body2).toContain('"max_output_tokens"')
+
+ // Clean up
+ delete (global as any).fetch
})
it("should support both verbosity and reasoning effort together for GPT-5", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"High verbosity minimal effort"}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
handler = new OpenAiNativeHandler({
...mockOptions,
apiModelId: "gpt-5-2025-08-07",
- verbosity: "high", // Set verbosity through options
- reasoningEffort: "low", // Set reasoning effort
+ verbosity: "high",
+ reasoningEffort: "minimal" as any,
})
const stream = handler.createMessage(systemPrompt, messages)
@@ -600,17 +956,863 @@ describe("OpenAiNativeHandler", () => {
chunks.push(chunk)
}
- // Verify both parameters are passed
- expect(mockCreate).toHaveBeenCalledWith(
+ // Should use Responses API with both parameters
+ expect(mockFetch).toHaveBeenCalledWith(
+ "https://api.openai.com/v1/responses",
expect.objectContaining({
- model: "gpt-5-2025-08-07",
- messages: expect.any(Array),
- stream: true,
- stream_options: { include_usage: true },
- reasoning_effort: "low",
- verbosity: "high",
+ body: expect.any(String),
}),
)
+ const body3 = (mockFetch.mock.calls[0][1] as any).body as string
+ expect(body3).toContain('"model":"gpt-5-2025-08-07"')
+ expect(body3).toContain('"effort":"minimal"')
+ expect(body3).toContain('"summary":"auto"')
+ expect(body3).toContain('"verbosity":"high"')
+ expect(body3).toContain('"temperature":1')
+ expect(body3).toContain('"max_output_tokens"')
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should handle actual GPT-5 Responses API format", async () => {
+ // Mock fetch with actual response format from GPT-5
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ // Test actual GPT-5 response format
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.created","response":{"id":"test","status":"in_progress"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.in_progress","response":{"status":"in_progress"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"First text"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":" Second text"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"reasoning","text":"Some reasoning"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.done","response":{"usage":{"prompt_tokens":100,"completion_tokens":20}}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-5-2025-08-07",
+ })
+
+ const stream = handler.createMessage(systemPrompt, messages)
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ // Should handle the actual format correctly
+ const textChunks = chunks.filter((c) => c.type === "text")
+ const reasoningChunks = chunks.filter((c) => c.type === "reasoning")
+
+ expect(textChunks).toHaveLength(2)
+ expect(textChunks[0].text).toBe("First text")
+ expect(textChunks[1].text).toBe(" Second text")
+
+ expect(reasoningChunks).toHaveLength(1)
+ expect(reasoningChunks[0].text).toBe("Some reasoning")
+
+ // Should also have usage information with cost
+ const usageChunks = chunks.filter((c) => c.type === "usage")
+ expect(usageChunks).toHaveLength(1)
+ expect(usageChunks[0]).toMatchObject({
+ type: "usage",
+ inputTokens: 100,
+ outputTokens: 20,
+ totalCost: expect.any(Number),
+ })
+
+ // Verify cost calculation (GPT-5 pricing: input $1.25/M, output $10/M)
+ const expectedInputCost = (100 / 1_000_000) * 1.25
+ const expectedOutputCost = (20 / 1_000_000) * 10.0
+ const expectedTotalCost = expectedInputCost + expectedOutputCost
+ expect(usageChunks[0].totalCost).toBeCloseTo(expectedTotalCost, 10)
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should handle Responses API with no content gracefully", async () => {
+ // Mock fetch with empty response
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(new TextEncoder().encode('data: {"someField":"value"}\n\n'))
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-5-2025-08-07",
+ })
+
+ const stream = handler.createMessage(systemPrompt, messages)
+ const chunks: any[] = []
+
+ // Should not throw, just warn
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ // Should have no content chunks when stream is empty
+ const contentChunks = chunks.filter((c) => c.type === "text" || c.type === "reasoning")
+
+ expect(contentChunks).toHaveLength(0)
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should support previous_response_id for conversation continuity", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ // Include response ID in the response
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.created","response":{"id":"resp_123","status":"in_progress"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Response with ID"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.done","response":{"id":"resp_123","usage":{"prompt_tokens":10,"completion_tokens":3}}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-5-2025-08-07",
+ })
+
+ // First request - should not have previous_response_id
+ const stream1 = handler.createMessage(systemPrompt, messages)
+ const chunks1: any[] = []
+ for await (const chunk of stream1) {
+ chunks1.push(chunk)
+ }
+
+ // Verify first request doesn't include previous_response_id
+ let firstCallBody = JSON.parse(mockFetch.mock.calls[0][1].body)
+ expect(firstCallBody.previous_response_id).toBeUndefined()
+
+ // Second request with metadata - should include previous_response_id
+ const stream2 = handler.createMessage(systemPrompt, messages, {
+ taskId: "test-task",
+ previousResponseId: "resp_456",
+ })
+ const chunks2: any[] = []
+ for await (const chunk of stream2) {
+ chunks2.push(chunk)
+ }
+
+ // Verify second request includes the provided previous_response_id
+ let secondCallBody = JSON.parse(mockFetch.mock.calls[1][1].body)
+ expect(secondCallBody.previous_response_id).toBe("resp_456")
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should handle unhandled stream events gracefully", async () => {
+ // Mock fetch for the fallback SSE path (which is what gets used when SDK fails)
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Hello"}}\n\n',
+ ),
+ )
+ // This event is not handled, so it should be ignored
+ controller.enqueue(
+ new TextEncoder().encode('data: {"type":"response.audio.delta","delta":"..."}\n\n'),
+ )
+ controller.enqueue(new TextEncoder().encode('data: {"type":"response.done","response":{}}\n\n'))
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ // Also mock the SDK to throw an error so it falls back to fetch
+ const mockClient = {
+ responses: {
+ create: vitest.fn().mockRejectedValue(new Error("SDK not available")),
+ },
+ }
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-5-2025-08-07",
+ })
+
+ // Replace the client with our mock
+ ;(handler as any).client = mockClient
+
+ const stream = handler.createMessage(systemPrompt, messages)
+ const chunks: any[] = []
+ const errors: any[] = []
+
+ try {
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+ } catch (error) {
+ errors.push(error)
+ }
+
+ // Log for debugging
+ if (chunks.length === 0 && errors.length === 0) {
+ console.log("No chunks and no errors received")
+ }
+ if (errors.length > 0) {
+ console.log("Errors:", errors)
+ }
+
+ expect(errors.length).toBe(0)
+ const textChunks = chunks.filter((c) => c.type === "text")
+ expect(textChunks.length).toBeGreaterThan(0)
+ expect(textChunks[0].text).toBe("Hello")
+
+ delete (global as any).fetch
+ })
+
+ it("should use stored response ID when metadata doesn't provide one", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest
+ .fn()
+ .mockResolvedValueOnce({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ // First response with ID
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.done","response":{"id":"resp_789","output":[{"type":"text","content":[{"type":"text","text":"First"}]}],"usage":{"prompt_tokens":10,"completion_tokens":1}}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ .mockResolvedValueOnce({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ // Second response
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Second"}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-5-2025-08-07",
+ })
+
+ // First request - establishes response ID
+ const stream1 = handler.createMessage(systemPrompt, messages)
+ for await (const chunk of stream1) {
+ // consume stream
+ }
+
+ // Second request without metadata - should use stored response ID
+ const stream2 = handler.createMessage(systemPrompt, messages, { taskId: "test-task" })
+ for await (const chunk of stream2) {
+ // consume stream
+ }
+
+ // Verify second request uses the stored response ID from first request
+ let secondCallBody = JSON.parse(mockFetch.mock.calls[1][1].body)
+ expect(secondCallBody.previous_response_id).toBe("resp_789")
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should only send latest message when using previous_response_id", async () => {
+ // Mock fetch for Responses API
+ const mockFetch = vitest
+ .fn()
+ .mockResolvedValueOnce({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ // First response with ID
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.done","response":{"id":"resp_001","output":[{"type":"text","content":[{"type":"text","text":"First"}]}],"usage":{"prompt_tokens":50,"completion_tokens":1}}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ .mockResolvedValueOnce({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ // Second response
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Second"}}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.done","response":{"id":"resp_002","usage":{"prompt_tokens":10,"completion_tokens":1}}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-5-2025-08-07",
+ })
+
+ // First request with full conversation
+ const firstMessages: Anthropic.Messages.MessageParam[] = [
+ { role: "user", content: "Hello" },
+ { role: "assistant", content: "Hi there!" },
+ { role: "user", content: "How are you?" },
+ ]
+
+ const stream1 = handler.createMessage(systemPrompt, firstMessages)
+ for await (const chunk of stream1) {
+ // consume stream
+ }
+
+ // Verify first request sends full conversation
+ let firstCallBody = JSON.parse(mockFetch.mock.calls[0][1].body)
+ expect(firstCallBody.input).toContain("Hello")
+ expect(firstCallBody.input).toContain("Hi there!")
+ expect(firstCallBody.input).toContain("How are you?")
+ expect(firstCallBody.previous_response_id).toBeUndefined()
+
+ // Second request with previous_response_id - should only send latest message
+ const secondMessages: Anthropic.Messages.MessageParam[] = [
+ { role: "user", content: "Hello" },
+ { role: "assistant", content: "Hi there!" },
+ { role: "user", content: "How are you?" },
+ { role: "assistant", content: "I'm doing well!" },
+ { role: "user", content: "What's the weather?" }, // Latest message
+ ]
+
+ const stream2 = handler.createMessage(systemPrompt, secondMessages, {
+ taskId: "test-task",
+ previousResponseId: "resp_001",
+ })
+ for await (const chunk of stream2) {
+ // consume stream
+ }
+
+ // Verify second request only sends the latest user message
+ let secondCallBody = JSON.parse(mockFetch.mock.calls[1][1].body)
+ expect(secondCallBody.input).toBe("User: What's the weather?")
+ expect(secondCallBody.input).not.toContain("Hello")
+ expect(secondCallBody.input).not.toContain("Hi there!")
+ expect(secondCallBody.input).not.toContain("How are you?")
+ expect(secondCallBody.previous_response_id).toBe("resp_001")
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should correctly prepare GPT-5 input with conversation continuity", () => {
+ const gpt5Handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-5-2025-08-07",
+ })
+
+ // @ts-expect-error - private method
+ const { formattedInput, previousResponseId } = gpt5Handler.prepareGpt5Input(systemPrompt, messages, {
+ taskId: "task1",
+ previousResponseId: "resp_123",
+ })
+
+ expect(previousResponseId).toBe("resp_123")
+ expect(formattedInput).toBe("User: Hello!")
+ })
+
+ it("should provide helpful error messages for different error codes", async () => {
+ const testCases = [
+ { status: 400, expectedMessage: "Invalid request to GPT-5 API" },
+ { status: 401, expectedMessage: "Authentication failed" },
+ { status: 403, expectedMessage: "Access denied" },
+ { status: 404, expectedMessage: "GPT-5 API endpoint not found" },
+ { status: 429, expectedMessage: "Rate limit exceeded" },
+ { status: 500, expectedMessage: "OpenAI service error" },
+ ]
+
+ for (const { status, expectedMessage } of testCases) {
+ // Mock fetch with error response
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: false,
+ status,
+ statusText: "Error",
+ text: async () => JSON.stringify({ error: { message: "Test error" } }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "gpt-5-2025-08-07",
+ })
+
+ const stream = handler.createMessage(systemPrompt, messages)
+
+ await expect(async () => {
+ for await (const chunk of stream) {
+ // Should throw before yielding anything
+ }
+ }).rejects.toThrow(expectedMessage)
+ }
+
+ // Clean up
+ delete (global as any).fetch
+ })
+ })
+})
+
+// Added tests for GPT-5 streaming event coverage per PR_review_gpt5_final.md
+
+describe("GPT-5 streaming event coverage (additional)", () => {
+ it("should handle reasoning delta events for GPT-5", async () => {
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.reasoning.delta","delta":"Thinking about the problem..."}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode('data: {"type":"response.text.delta","delta":"The answer is..."}\n\n'),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ // @ts-ignore
+ global.fetch = mockFetch
+
+ const handler = new OpenAiNativeHandler({
+ apiModelId: "gpt-5-2025-08-07",
+ openAiNativeApiKey: "test-api-key",
+ })
+
+ const systemPrompt = "You are a helpful assistant."
+ const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello!" }]
+ const stream = handler.createMessage(systemPrompt, messages)
+
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ const reasoningChunks = chunks.filter((c) => c.type === "reasoning")
+ const textChunks = chunks.filter((c) => c.type === "text")
+
+ expect(reasoningChunks).toHaveLength(1)
+ expect(reasoningChunks[0].text).toBe("Thinking about the problem...")
+ expect(textChunks).toHaveLength(1)
+ expect(textChunks[0].text).toBe("The answer is...")
+
+ // @ts-ignore
+ delete global.fetch
+ })
+
+ it("should handle refusal delta events for GPT-5 and prefix output", async () => {
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.refusal.delta","delta":"I cannot comply with this request."}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ // @ts-ignore
+ global.fetch = mockFetch
+
+ const handler = new OpenAiNativeHandler({
+ apiModelId: "gpt-5-2025-08-07",
+ openAiNativeApiKey: "test-api-key",
+ })
+
+ const systemPrompt = "You are a helpful assistant."
+ const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Do something disallowed" }]
+ const stream = handler.createMessage(systemPrompt, messages)
+
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ const textChunks = chunks.filter((c) => c.type === "text")
+ expect(textChunks).toHaveLength(1)
+ expect(textChunks[0].text).toBe("[Refusal] I cannot comply with this request.")
+
+ // @ts-ignore
+ delete global.fetch
+ })
+
+ it("should ignore malformed JSON lines in SSE stream", async () => {
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"Before"}}\n\n',
+ ),
+ )
+ // Malformed JSON line
+ controller.enqueue(
+ new TextEncoder().encode('data: {"type":"response.text.delta","delta":"Bad"\n\n'),
+ )
+ // Valid line after malformed
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_item.added","item":{"type":"text","text":"After"}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ // @ts-ignore
+ global.fetch = mockFetch
+
+ const handler = new OpenAiNativeHandler({
+ apiModelId: "gpt-5-2025-08-07",
+ openAiNativeApiKey: "test-api-key",
+ })
+
+ const systemPrompt = "You are a helpful assistant."
+ const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello!" }]
+ const stream = handler.createMessage(systemPrompt, messages)
+
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ // It should not throw and still capture the valid texts around the malformed line
+ const textChunks = chunks.filter((c) => c.type === "text")
+ expect(textChunks.map((c: any) => c.text)).toEqual(["Before", "After"])
+
+ // @ts-ignore
+ delete global.fetch
+ })
+
+ describe("Codex Mini Model", () => {
+ let handler: OpenAiNativeHandler
+ const mockOptions: ApiHandlerOptions = {
+ openAiNativeApiKey: "test-api-key",
+ apiModelId: "codex-mini-latest",
+ }
+
+ it("should handle codex-mini-latest streaming response", async () => {
+ // Mock fetch for Codex Mini responses API
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ // Codex Mini uses the same responses API format
+ controller.enqueue(
+ new TextEncoder().encode('data: {"type":"response.output_text.delta","delta":"Hello"}\n\n'),
+ )
+ controller.enqueue(
+ new TextEncoder().encode('data: {"type":"response.output_text.delta","delta":" from"}\n\n'),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_text.delta","delta":" Codex"}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_text.delta","delta":" Mini!"}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.done","response":{"usage":{"prompt_tokens":50,"completion_tokens":10}}}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "codex-mini-latest",
+ })
+
+ const systemPrompt = "You are a helpful coding assistant."
+ const messages: Anthropic.Messages.MessageParam[] = [
+ { role: "user", content: "Write a hello world function" },
+ ]
+
+ const stream = handler.createMessage(systemPrompt, messages)
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ // Verify text chunks
+ const textChunks = chunks.filter((c) => c.type === "text")
+ expect(textChunks).toHaveLength(4)
+ expect(textChunks.map((c) => c.text).join("")).toBe("Hello from Codex Mini!")
+
+ // Verify usage data from API
+ const usageChunks = chunks.filter((c) => c.type === "usage")
+ expect(usageChunks).toHaveLength(1)
+ expect(usageChunks[0]).toMatchObject({
+ type: "usage",
+ inputTokens: 50,
+ outputTokens: 10,
+ totalCost: expect.any(Number), // Codex Mini has pricing: $1.5/M input, $6/M output
+ })
+
+ // Verify cost is calculated correctly based on API usage data
+ const expectedCost = (50 / 1_000_000) * 1.5 + (10 / 1_000_000) * 6
+ expect(usageChunks[0].totalCost).toBeCloseTo(expectedCost, 10)
+
+ // Verify the request was made with correct parameters
+ expect(mockFetch).toHaveBeenCalledWith(
+ "https://api.openai.com/v1/responses",
+ expect.objectContaining({
+ method: "POST",
+ headers: expect.objectContaining({
+ "Content-Type": "application/json",
+ Authorization: "Bearer test-api-key",
+ Accept: "text/event-stream",
+ }),
+ body: expect.any(String),
+ }),
+ )
+
+ const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
+ expect(requestBody).toMatchObject({
+ model: "codex-mini-latest",
+ input: "Developer: You are a helpful coding assistant.\n\nUser: Write a hello world function",
+ stream: true,
+ })
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should handle codex-mini-latest non-streaming completion", async () => {
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "codex-mini-latest",
+ })
+
+ // Codex Mini now uses the same Responses API as GPT-5, which doesn't support non-streaming
+ await expect(handler.completePrompt("Write a hello world function in Python")).rejects.toThrow(
+ "completePrompt is not supported for codex-mini-latest. Use createMessage (Responses API) instead.",
+ )
+ })
+
+ it("should handle codex-mini-latest API errors", async () => {
+ // Mock fetch with error response
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: false,
+ status: 429,
+ statusText: "Too Many Requests",
+ text: async () => "Rate limit exceeded",
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "codex-mini-latest",
+ })
+
+ const systemPrompt = "You are a helpful assistant."
+ const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
+
+ const stream = handler.createMessage(systemPrompt, messages)
+
+ // Should throw an error (using the same error format as GPT-5)
+ await expect(async () => {
+ for await (const chunk of stream) {
+ // consume stream
+ }
+ }).rejects.toThrow("Rate limit exceeded")
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should handle codex-mini-latest with multiple user messages", async () => {
+ // Mock fetch for streaming response
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_text.delta","delta":"Combined response"}\n\n',
+ ),
+ )
+ controller.enqueue(new TextEncoder().encode('data: {"type":"response.completed"}\n\n'))
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "codex-mini-latest",
+ })
+
+ const systemPrompt = "You are a helpful assistant."
+ const messages: Anthropic.Messages.MessageParam[] = [
+ { role: "user", content: "First question" },
+ { role: "assistant", content: "First answer" },
+ { role: "user", content: "Second question" },
+ ]
+
+ const stream = handler.createMessage(systemPrompt, messages)
+ const chunks: any[] = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+
+ // Verify the request body includes full conversation like GPT-5
+ const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
+ expect(requestBody.input).toContain("Developer: You are a helpful assistant")
+ expect(requestBody.input).toContain("User: First question")
+ expect(requestBody.input).toContain("Assistant: First answer")
+ expect(requestBody.input).toContain("User: Second question")
+
+ // Clean up
+ delete (global as any).fetch
+ })
+
+ it("should handle codex-mini-latest stream error events", async () => {
+ // Mock fetch with error event in stream
+ const mockFetch = vitest.fn().mockResolvedValue({
+ ok: true,
+ body: new ReadableStream({
+ start(controller) {
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.output_text.delta","delta":"Partial"}\n\n',
+ ),
+ )
+ controller.enqueue(
+ new TextEncoder().encode(
+ 'data: {"type":"response.error","error":{"message":"Model overloaded"}}\n\n',
+ ),
+ )
+ // The error handler will throw, but we still need to close the stream
+ controller.close()
+ },
+ }),
+ })
+ global.fetch = mockFetch as any
+
+ handler = new OpenAiNativeHandler({
+ ...mockOptions,
+ apiModelId: "codex-mini-latest",
+ })
+
+ const systemPrompt = "You are a helpful assistant."
+ const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
+
+ const stream = handler.createMessage(systemPrompt, messages)
+
+ // Should throw an error when encountering error event
+ await expect(async () => {
+ const chunks = []
+ for await (const chunk of stream) {
+ chunks.push(chunk)
+ }
+ }).rejects.toThrow("Responses API error: Model overloaded")
+
+ // Clean up
+ delete (global as any).fetch
})
})
})
diff --git a/src/api/providers/__tests__/openai-timeout.spec.ts b/src/api/providers/__tests__/openai-timeout.spec.ts
new file mode 100644
index 0000000000..2a09fd94ff
--- /dev/null
+++ b/src/api/providers/__tests__/openai-timeout.spec.ts
@@ -0,0 +1,144 @@
+// npx vitest run api/providers/__tests__/openai-timeout.spec.ts
+
+import { OpenAiHandler } from "../openai"
+import { ApiHandlerOptions } from "../../../shared/api"
+
+// Mock the timeout config utility
+vitest.mock("../utils/timeout-config", () => ({
+ getApiRequestTimeout: vitest.fn(),
+}))
+
+import { getApiRequestTimeout } from "../utils/timeout-config"
+
+// Mock OpenAI and AzureOpenAI
+const mockOpenAIConstructor = vitest.fn()
+const mockAzureOpenAIConstructor = vitest.fn()
+
+vitest.mock("openai", () => {
+ return {
+ __esModule: true,
+ default: vitest.fn().mockImplementation((config) => {
+ mockOpenAIConstructor(config)
+ return {
+ chat: {
+ completions: {
+ create: vitest.fn(),
+ },
+ },
+ }
+ }),
+ AzureOpenAI: vitest.fn().mockImplementation((config) => {
+ mockAzureOpenAIConstructor(config)
+ return {
+ chat: {
+ completions: {
+ create: vitest.fn(),
+ },
+ },
+ }
+ }),
+ }
+})
+
+describe("OpenAiHandler timeout configuration", () => {
+ beforeEach(() => {
+ vitest.clearAllMocks()
+ })
+
+ it("should use default timeout for standard OpenAI", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(600000)
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "gpt-4",
+ openAiModelId: "gpt-4",
+ openAiApiKey: "test-key",
+ }
+
+ new OpenAiHandler(options)
+
+ expect(getApiRequestTimeout).toHaveBeenCalled()
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ baseURL: "https://api.openai.com/v1",
+ apiKey: "test-key",
+ timeout: 600000, // 600 seconds in milliseconds
+ }),
+ )
+ })
+
+ it("should use custom timeout for OpenAI-compatible providers", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(1800000) // 30 minutes
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "custom-model",
+ openAiModelId: "custom-model",
+ openAiBaseUrl: "http://localhost:8080/v1",
+ openAiApiKey: "test-key",
+ }
+
+ new OpenAiHandler(options)
+
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ baseURL: "http://localhost:8080/v1",
+ timeout: 1800000, // 1800 seconds in milliseconds
+ }),
+ )
+ })
+
+ it("should use timeout for Azure OpenAI", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(900000) // 15 minutes
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "gpt-4",
+ openAiModelId: "gpt-4",
+ openAiBaseUrl: "https://myinstance.openai.azure.com",
+ openAiApiKey: "test-key",
+ openAiUseAzure: true,
+ }
+
+ new OpenAiHandler(options)
+
+ expect(mockAzureOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ timeout: 900000, // 900 seconds in milliseconds
+ }),
+ )
+ })
+
+ it("should use timeout for Azure AI Inference", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(1200000) // 20 minutes
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "deepseek",
+ openAiModelId: "deepseek",
+ openAiBaseUrl: "https://myinstance.services.ai.azure.com",
+ openAiApiKey: "test-key",
+ }
+
+ new OpenAiHandler(options)
+
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ timeout: 1200000, // 1200 seconds in milliseconds
+ }),
+ )
+ })
+
+ it("should handle zero timeout (no timeout)", () => {
+ ;(getApiRequestTimeout as any).mockReturnValue(0)
+
+ const options: ApiHandlerOptions = {
+ apiModelId: "gpt-4",
+ openAiModelId: "gpt-4",
+ }
+
+ new OpenAiHandler(options)
+
+ expect(mockOpenAIConstructor).toHaveBeenCalledWith(
+ expect.objectContaining({
+ timeout: 0, // No timeout
+ }),
+ )
+ })
+})
diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts
index 0d42c082a9..3e744d6e16 100644
--- a/src/api/providers/__tests__/openai.spec.ts
+++ b/src/api/providers/__tests__/openai.spec.ts
@@ -115,6 +115,7 @@ describe("OpenAiHandler", () => {
"X-Title": "Roo Code",
"User-Agent": `RooCode/${Package.version}`,
},
+ timeout: expect.any(Number),
})
})
})
diff --git a/src/api/providers/__tests__/requesty.spec.ts b/src/api/providers/__tests__/requesty.spec.ts
index 55fb976fd1..72d4aa790b 100644
--- a/src/api/providers/__tests__/requesty.spec.ts
+++ b/src/api/providers/__tests__/requesty.spec.ts
@@ -65,6 +65,21 @@ describe("RequestyHandler", () => {
})
})
+ it("can use a base URL instead of the default", () => {
+ const handler = new RequestyHandler({ ...mockOptions, requestyBaseUrl: "some-base-url" })
+ expect(handler).toBeInstanceOf(RequestyHandler)
+
+ expect(OpenAI).toHaveBeenCalledWith({
+ baseURL: "some-base-url",
+ apiKey: mockOptions.requestyApiKey,
+ defaultHeaders: {
+ "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline",
+ "X-Title": "Roo Code",
+ "User-Agent": `RooCode/${Package.version}`,
+ },
+ })
+ })
+
describe("fetchModel", () => {
it("returns correct model info when options are provided", async () => {
const handler = new RequestyHandler(mockOptions)
diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts
index f456586762..cb48492b60 100644
--- a/src/api/providers/anthropic.ts
+++ b/src/api/providers/anthropic.ts
@@ -45,6 +45,11 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
const cacheControl: CacheControlEphemeral = { type: "ephemeral" }
let { id: modelId, betas = [], maxTokens, temperature, reasoning: thinking } = this.getModel()
+ // Add 1M context beta flag if enabled for Claude Sonnet 4
+ if (modelId === "claude-sonnet-4-20250514" && this.options.anthropicBeta1MContext) {
+ betas.push("context-1m-2025-08-07")
+ }
+
switch (modelId) {
case "claude-sonnet-4-20250514":
case "claude-opus-4-1-20250805":
@@ -236,7 +241,23 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
getModel() {
const modelId = this.options.apiModelId
let id = modelId && modelId in anthropicModels ? (modelId as AnthropicModelId) : anthropicDefaultModelId
- const info: ModelInfo = anthropicModels[id]
+ let info: ModelInfo = anthropicModels[id]
+
+ // If 1M context beta is enabled for Claude Sonnet 4, update the model info
+ if (id === "claude-sonnet-4-20250514" && this.options.anthropicBeta1MContext) {
+ // Use the tier pricing for 1M context
+ const tier = info.tiers?.[0]
+ if (tier) {
+ info = {
+ ...info,
+ contextWindow: tier.contextWindow,
+ inputPrice: tier.inputPrice,
+ outputPrice: tier.outputPrice,
+ cacheWritesPrice: tier.cacheWritesPrice,
+ cacheReadsPrice: tier.cacheReadsPrice,
+ }
+ }
+ }
const params = getModelParams({
format: "anthropic",
diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts
index 76e502e6c7..c6a0b35df4 100644
--- a/src/api/providers/bedrock.ts
+++ b/src/api/providers/bedrock.ts
@@ -21,6 +21,7 @@ import {
BEDROCK_MAX_TOKENS,
BEDROCK_DEFAULT_CONTEXT,
AWS_INFERENCE_PROFILE_MAPPING,
+ BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
} from "@roo-code/types"
import { ApiStream } from "../transform/stream"
@@ -46,12 +47,14 @@ interface BedrockInferenceConfig {
topP?: number
}
-// Define interface for Bedrock thinking configuration
-interface BedrockThinkingConfig {
- thinking: {
+// Define interface for Bedrock additional model request fields
+// This includes thinking configuration, 1M context beta, and other model-specific parameters
+interface BedrockAdditionalModelFields {
+ thinking?: {
type: "enabled"
budget_tokens: number
}
+ anthropic_beta?: string[]
[key: string]: any // Add index signature to be compatible with DocumentType
}
@@ -62,7 +65,7 @@ interface BedrockPayload {
system?: SystemContentBlock[]
inferenceConfig: BedrockInferenceConfig
anthropic_version?: string
- additionalModelRequestFields?: BedrockThinkingConfig
+ additionalModelRequestFields?: BedrockAdditionalModelFields
}
// Define specific types for content block events to avoid 'as any' usage
@@ -226,6 +229,11 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
// Use API key/token-based authentication if enabled and API key is set
clientConfig.token = { token: this.options.awsApiKey }
clientConfig.authSchemePreference = ["httpBearerAuth"] // Otherwise there's no end of credential problems.
+ clientConfig.requestHandler = {
+ // This should be the default anyway, but without setting something
+ // this provider fails to work with LiteLLM passthrough.
+ requestTimeout: 0,
+ }
} else if (this.options.awsUseProfile && this.options.awsProfile) {
// Use profile-based credentials if enabled and profile is set
clientConfig.credentials = fromIni({
@@ -334,7 +342,7 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
conversationId,
)
- let additionalModelRequestFields: BedrockThinkingConfig | undefined
+ let additionalModelRequestFields: BedrockAdditionalModelFields | undefined
let thinkingEnabled = false
// Determine if thinking should be enabled
@@ -370,13 +378,26 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
inferenceConfig.topP = 0.1
}
+ // Check if 1M context is enabled for Claude Sonnet 4
+ // Use parseBaseModelId to handle cross-region inference prefixes
+ const baseModelId = this.parseBaseModelId(modelConfig.id)
+ const is1MContextEnabled = baseModelId === BEDROCK_CLAUDE_SONNET_4_MODEL_ID && this.options.awsBedrock1MContext
+
+ // Add anthropic_beta for 1M context to additionalModelRequestFields
+ if (is1MContextEnabled) {
+ if (!additionalModelRequestFields) {
+ additionalModelRequestFields = {} as BedrockAdditionalModelFields
+ }
+ additionalModelRequestFields.anthropic_beta = ["context-1m-2025-08-07"]
+ }
+
const payload: BedrockPayload = {
modelId: modelConfig.id,
messages: formatted.messages,
system: formatted.system,
inferenceConfig,
...(additionalModelRequestFields && { additionalModelRequestFields }),
- // Add anthropic_version when using thinking features
+ // Add anthropic_version at top level when using thinking features
...(thinkingEnabled && { anthropic_version: "bedrock-2023-05-31" }),
}
@@ -955,6 +976,17 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
}
}
+ // Check if 1M context is enabled for Claude Sonnet 4
+ // Use parseBaseModelId to handle cross-region inference prefixes
+ const baseModelId = this.parseBaseModelId(modelConfig.id)
+ if (baseModelId === BEDROCK_CLAUDE_SONNET_4_MODEL_ID && this.options.awsBedrock1MContext) {
+ // Update context window to 1M tokens when 1M context beta is enabled
+ modelConfig.info = {
+ ...modelConfig.info,
+ contextWindow: 1_000_000,
+ }
+ }
+
// Get model params including reasoning configuration
const params = getModelParams({
format: "anthropic",
diff --git a/src/api/providers/fetchers/__tests__/modelCache.spec.ts b/src/api/providers/fetchers/__tests__/modelCache.spec.ts
index 69369a2ce8..21f5ce8bff 100644
--- a/src/api/providers/fetchers/__tests__/modelCache.spec.ts
+++ b/src/api/providers/fetchers/__tests__/modelCache.spec.ts
@@ -24,6 +24,7 @@ vi.mock("../openrouter")
vi.mock("../requesty")
vi.mock("../glama")
vi.mock("../unbound")
+vi.mock("../io-intelligence")
// Then imports
import type { Mock } from "vitest"
@@ -33,15 +34,18 @@ import { getOpenRouterModels } from "../openrouter"
import { getRequestyModels } from "../requesty"
import { getGlamaModels } from "../glama"
import { getUnboundModels } from "../unbound"
+import { getIOIntelligenceModels } from "../io-intelligence"
const mockGetLiteLLMModels = getLiteLLMModels as Mock
const mockGetOpenRouterModels = getOpenRouterModels as Mock
const mockGetRequestyModels = getRequestyModels as Mock
const mockGetGlamaModels = getGlamaModels as Mock
const mockGetUnboundModels = getUnboundModels as Mock
+const mockGetIOIntelligenceModels = getIOIntelligenceModels as Mock
const DUMMY_REQUESTY_KEY = "requesty-key-for-testing"
const DUMMY_UNBOUND_KEY = "unbound-key-for-testing"
+const DUMMY_IOINTELLIGENCE_KEY = "io-intelligence-key-for-testing"
describe("getModels with new GetModelsOptions", () => {
beforeEach(() => {
@@ -137,6 +141,23 @@ describe("getModels with new GetModelsOptions", () => {
expect(result).toEqual(mockModels)
})
+ it("calls IOIntelligenceModels for IO-Intelligence provider", async () => {
+ const mockModels = {
+ "io-intelligence/model": {
+ maxTokens: 4096,
+ contextWindow: 8192,
+ supportsPromptCache: false,
+ description: "IO Intelligence Model",
+ },
+ }
+ mockGetIOIntelligenceModels.mockResolvedValue(mockModels)
+
+ const result = await getModels({ provider: "io-intelligence", apiKey: DUMMY_IOINTELLIGENCE_KEY })
+
+ expect(mockGetIOIntelligenceModels).toHaveBeenCalled()
+ expect(result).toEqual(mockModels)
+ })
+
it("handles errors and re-throws them", async () => {
const expectedError = new Error("LiteLLM connection failed")
mockGetLiteLLMModels.mockRejectedValue(expectedError)
diff --git a/src/api/providers/fetchers/io-intelligence.ts b/src/api/providers/fetchers/io-intelligence.ts
new file mode 100644
index 0000000000..326cefb0cc
--- /dev/null
+++ b/src/api/providers/fetchers/io-intelligence.ts
@@ -0,0 +1,189 @@
+import axios from "axios"
+import { z } from "zod"
+import type { ModelInfo } from "@roo-code/types"
+import { IO_INTELLIGENCE_CACHE_DURATION } from "@roo-code/types"
+import type { ModelRecord } from "../../../shared/api"
+
+/**
+ * IO Intelligence Model Schema
+ */
+const ioIntelligenceModelSchema = z.object({
+ id: z.string(),
+ object: z.literal("model"),
+ created: z.number(),
+ owned_by: z.string(),
+ root: z.string().nullable().optional(),
+ parent: z.string().nullable().optional(),
+ max_model_len: z.number().nullable().optional(),
+ permission: z.array(
+ z.object({
+ id: z.string(),
+ object: z.literal("model_permission"),
+ created: z.number(),
+ allow_create_engine: z.boolean(),
+ allow_sampling: z.boolean(),
+ allow_logprobs: z.boolean(),
+ allow_search_indices: z.boolean(),
+ allow_view: z.boolean(),
+ allow_fine_tuning: z.boolean(),
+ organization: z.string(),
+ group: z.string().nullable(),
+ is_blocking: z.boolean(),
+ }),
+ ),
+})
+
+export type IOIntelligenceModel = z.infer
+
+/**
+ * IO Intelligence API Response Schema
+ */
+const ioIntelligenceApiResponseSchema = z.object({
+ object: z.literal("list"),
+ data: z.array(ioIntelligenceModelSchema),
+})
+
+type IOIntelligenceApiResponse = z.infer
+
+/**
+ * Cache entry for storing fetched models
+ */
+interface CacheEntry {
+ data: ModelRecord
+ timestamp: number
+}
+
+let cache: CacheEntry | null = null
+
+/**
+ * Model context length mapping based on the documentation
+ * 1
+ */
+const MODEL_CONTEXT_LENGTHS: Record = {
+ "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": 430000,
+ "deepseek-ai/DeepSeek-R1-0528": 128000,
+ "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar": 106000,
+ "openai/gpt-oss-120b": 131072,
+}
+
+/**
+ * Vision models that support images
+ */
+const VISION_MODELS = new Set([
+ "Qwen/Qwen2.5-VL-32B-Instruct",
+ "meta-llama/Llama-3.2-90B-Vision-Instruct",
+ "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
+])
+
+/**
+ * Parse an IO Intelligence model into ModelInfo format
+ */
+function parseIOIntelligenceModel(model: IOIntelligenceModel): ModelInfo {
+ const contextLength = MODEL_CONTEXT_LENGTHS[model.id] || 8192
+ // Cap maxTokens at 32k for very large context windows, or 20% of context length, whichever is smaller
+ const maxTokens = Math.min(contextLength, Math.ceil(contextLength * 0.2), 32768)
+ const supportsImages = VISION_MODELS.has(model.id)
+
+ return {
+ maxTokens,
+ contextWindow: contextLength,
+ supportsImages,
+ supportsPromptCache: false,
+ supportsComputerUse: false,
+ description: `${model.id} via IO Intelligence`,
+ }
+}
+
+/**
+ * Fetches available models from IO Intelligence
+ * 1
+ */
+export async function getIOIntelligenceModels(apiKey?: string): Promise {
+ const now = Date.now()
+
+ // Check cache
+ if (cache && now - cache.timestamp < IO_INTELLIGENCE_CACHE_DURATION) {
+ return cache.data
+ }
+
+ const models: ModelRecord = {}
+
+ try {
+ const headers: Record = {
+ "Content-Type": "application/json",
+ }
+
+ // Add authorization header if API key is provided
+ if (apiKey) {
+ headers.Authorization = `Bearer ${apiKey}`
+ } else {
+ console.error("IO Intelligence API key is required")
+ throw new Error("IO Intelligence API key is required")
+ }
+
+ const response = await axios.get(
+ "https://api.intelligence.io.solutions/api/v1/models",
+ {
+ headers,
+ timeout: 10000, // 10 second timeout
+ },
+ )
+
+ const result = ioIntelligenceApiResponseSchema.safeParse(response.data)
+
+ if (!result.success) {
+ console.error("IO Intelligence models response validation failed:", result.error.format())
+ throw new Error("Invalid response format from IO Intelligence API")
+ }
+
+ for (const model of result.data.data) {
+ models[model.id] = parseIOIntelligenceModel(model)
+ }
+
+ // Update cache
+ cache = {
+ data: models,
+ timestamp: now,
+ }
+
+ return models
+ } catch (error) {
+ console.error("Error fetching IO Intelligence models:", error)
+
+ // Return cached data if available
+ if (cache) {
+ return cache.data
+ }
+
+ // Re-throw with more context
+ if (axios.isAxiosError(error)) {
+ if (error.response) {
+ throw new Error(
+ `Failed to fetch IO Intelligence models: ${error.response.status} ${error.response.statusText}`,
+ )
+ } else if (error.request) {
+ throw new Error(
+ "Failed to fetch IO Intelligence models: No response from server. Check your internet connection.",
+ )
+ }
+ }
+
+ throw new Error(
+ `Failed to fetch IO Intelligence models: ${error instanceof Error ? error.message : "Unknown error"}`,
+ )
+ }
+}
+
+/**
+ * Get cached models without making an API request
+ */
+export function getCachedIOIntelligenceModels(): ModelRecord | null {
+ return cache?.data || null
+}
+
+/**
+ * Clear the cache
+ */
+export function clearIOIntelligenceCache(): void {
+ cache = null
+}
diff --git a/src/api/providers/fetchers/modelCache.ts b/src/api/providers/fetchers/modelCache.ts
index dd6bc01ba1..a21e75ded9 100644
--- a/src/api/providers/fetchers/modelCache.ts
+++ b/src/api/providers/fetchers/modelCache.ts
@@ -17,7 +17,7 @@ import { getLiteLLMModels } from "./litellm"
import { GetModelsOptions } from "../../../shared/api"
import { getOllamaModels } from "./ollama"
import { getLMStudioModels } from "./lmstudio"
-
+import { getIOIntelligenceModels } from "./io-intelligence"
const memoryCache = new NodeCache({ stdTTL: 5 * 60, checkperiod: 5 * 60 })
async function writeModels(router: RouterName, data: ModelRecord) {
@@ -78,6 +78,9 @@ export const getModels = async (options: GetModelsOptions): Promise
case "lmstudio":
models = await getLMStudioModels(options.baseUrl)
break
+ case "io-intelligence":
+ models = await getIOIntelligenceModels(options.apiKey)
+ break
default: {
// Ensures router is exhaustively checked if RouterName is a strict union
const exhaustiveCheck: never = provider
diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts
index 890999aa25..736da82d51 100644
--- a/src/api/providers/index.ts
+++ b/src/api/providers/index.ts
@@ -13,6 +13,7 @@ export { GlamaHandler } from "./glama"
export { GroqHandler } from "./groq"
export { HuggingFaceHandler } from "./huggingface"
export { HumanRelayHandler } from "./human-relay"
+export { IOIntelligenceHandler } from "./io-intelligence"
export { LiteLLMHandler } from "./lite-llm"
export { LmStudioHandler } from "./lm-studio"
export { MistralHandler } from "./mistral"
diff --git a/src/api/providers/io-intelligence.ts b/src/api/providers/io-intelligence.ts
new file mode 100644
index 0000000000..cbfb3129f9
--- /dev/null
+++ b/src/api/providers/io-intelligence.ts
@@ -0,0 +1,42 @@
+import { ioIntelligenceDefaultModelId, ioIntelligenceModels, type IOIntelligenceModelId } from "@roo-code/types"
+
+import type { ApiHandlerOptions } from "../../shared/api"
+import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
+
+export class IOIntelligenceHandler extends BaseOpenAiCompatibleProvider {
+ constructor(options: ApiHandlerOptions) {
+ if (!options.ioIntelligenceApiKey) {
+ throw new Error("IO Intelligence API key is required")
+ }
+
+ super({
+ ...options,
+ providerName: "IO Intelligence",
+ baseURL: "https://api.intelligence.io.solutions/api/v1",
+ defaultProviderModelId: ioIntelligenceDefaultModelId,
+ providerModels: ioIntelligenceModels,
+ defaultTemperature: 0.7,
+ apiKey: options.ioIntelligenceApiKey,
+ })
+ }
+ override getModel() {
+ const modelId = this.options.ioIntelligenceModelId || (ioIntelligenceDefaultModelId as IOIntelligenceModelId)
+ const modelInfo =
+ this.providerModels[modelId as IOIntelligenceModelId] ?? this.providerModels[ioIntelligenceDefaultModelId]
+
+ if (modelInfo) {
+ return { id: modelId as IOIntelligenceModelId, info: modelInfo }
+ }
+
+ // Return the requested model ID even if not found, with fallback info
+ return {
+ id: modelId as IOIntelligenceModelId,
+ info: {
+ maxTokens: 8192,
+ contextWindow: 128000,
+ supportsImages: false,
+ supportsPromptCache: false,
+ },
+ }
+ }
+}
diff --git a/src/api/providers/lm-studio.ts b/src/api/providers/lm-studio.ts
index 6c49920bd1..f3af46d1ce 100644
--- a/src/api/providers/lm-studio.ts
+++ b/src/api/providers/lm-studio.ts
@@ -14,6 +14,7 @@ import { ApiStream } from "../transform/stream"
import { BaseProvider } from "./base-provider"
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
import { getModels, getModelsFromCache } from "./fetchers/modelCache"
+import { getApiRequestTimeout } from "./utils/timeout-config"
export class LmStudioHandler extends BaseProvider implements SingleCompletionHandler {
protected options: ApiHandlerOptions
@@ -22,9 +23,11 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
constructor(options: ApiHandlerOptions) {
super()
this.options = options
+
this.client = new OpenAI({
baseURL: (this.options.lmStudioBaseUrl || "http://localhost:1234") + "/v1",
apiKey: "noop",
+ timeout: getApiRequestTimeout(),
})
}
diff --git a/src/api/providers/ollama.ts b/src/api/providers/ollama.ts
index a7713ba421..54666be58d 100644
--- a/src/api/providers/ollama.ts
+++ b/src/api/providers/ollama.ts
@@ -13,6 +13,7 @@ import { ApiStream } from "../transform/stream"
import { BaseProvider } from "./base-provider"
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
+import { getApiRequestTimeout } from "./utils/timeout-config"
type CompletionUsage = OpenAI.Chat.Completions.ChatCompletionChunk["usage"]
@@ -23,9 +24,11 @@ export class OllamaHandler extends BaseProvider implements SingleCompletionHandl
constructor(options: ApiHandlerOptions) {
super()
this.options = options
+
this.client = new OpenAI({
baseURL: (this.options.ollamaBaseUrl || "http://localhost:11434") + "/v1",
apiKey: "ollama",
+ timeout: getApiRequestTimeout(),
})
}
diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts
index 5e498bee45..2ba8566963 100644
--- a/src/api/providers/openai-native.ts
+++ b/src/api/providers/openai-native.ts
@@ -7,8 +7,10 @@ import {
OpenAiNativeModelId,
openAiNativeModels,
OPENAI_NATIVE_DEFAULT_TEMPERATURE,
+ GPT5_DEFAULT_TEMPERATURE,
type ReasoningEffort,
type VerbosityLevel,
+ type ReasoningEffortWithMinimal,
} from "@roo-code/types"
import type { ApiHandlerOptions } from "../../shared/api"
@@ -16,7 +18,7 @@ import type { ApiHandlerOptions } from "../../shared/api"
import { calculateApiCostOpenAI } from "../../shared/cost"
import { convertToOpenAiMessages } from "../transform/openai-format"
-import { ApiStream } from "../transform/stream"
+import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
import { getModelParams } from "../transform/model-params"
import { BaseProvider } from "./base-provider"
@@ -24,43 +26,77 @@ import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from ".
export type OpenAiNativeModel = ReturnType
-// GPT-5 specific types for Responses API
-type ReasoningEffortWithMinimal = ReasoningEffort | "minimal"
-
-interface GPT5ResponsesAPIParams {
- model: string
- input: string
- reasoning?: {
- effort: ReasoningEffortWithMinimal
- }
- text?: {
- verbosity: VerbosityLevel
- }
-}
-
-interface GPT5ResponseChunk {
- type: "text" | "reasoning" | "usage"
- text?: string
- reasoning?: string
- usage?: {
- input_tokens: number
- output_tokens: number
- reasoning_tokens?: number
- total_tokens: number
- }
-}
+// GPT-5 specific types
export class OpenAiNativeHandler extends BaseProvider implements SingleCompletionHandler {
protected options: ApiHandlerOptions
private client: OpenAI
+ private lastResponseId: string | undefined
+ private responseIdPromise: Promise | undefined
+ private responseIdResolver: ((value: string | undefined) => void) | undefined
+
+ // Event types handled by the shared GPT-5 event processor to avoid duplication
+ private readonly gpt5CoreHandledTypes = new Set([
+ "response.text.delta",
+ "response.output_text.delta",
+ "response.reasoning.delta",
+ "response.reasoning_text.delta",
+ "response.reasoning_summary.delta",
+ "response.reasoning_summary_text.delta",
+ "response.refusal.delta",
+ "response.output_item.added",
+ "response.done",
+ "response.completed",
+ ])
constructor(options: ApiHandlerOptions) {
super()
this.options = options
+ // Default to including reasoning.summary: "auto" for GPT‑5 unless explicitly disabled
+ if (this.options.enableGpt5ReasoningSummary === undefined) {
+ this.options.enableGpt5ReasoningSummary = true
+ }
const apiKey = this.options.openAiNativeApiKey ?? "not-provided"
this.client = new OpenAI({ baseURL: this.options.openAiNativeBaseUrl, apiKey })
}
+ private normalizeGpt5Usage(usage: any, model: OpenAiNativeModel): ApiStreamUsageChunk | undefined {
+ if (!usage) return undefined
+
+ const totalInputTokens = usage.input_tokens ?? usage.prompt_tokens ?? 0
+ const totalOutputTokens = usage.output_tokens ?? usage.completion_tokens ?? 0
+ const cacheWriteTokens = usage.cache_creation_input_tokens ?? usage.cache_write_tokens ?? 0
+ const cacheReadTokens = usage.cache_read_input_tokens ?? usage.cache_read_tokens ?? usage.cached_tokens ?? 0
+
+ const totalCost = calculateApiCostOpenAI(
+ model.info,
+ totalInputTokens,
+ totalOutputTokens,
+ cacheWriteTokens || 0,
+ cacheReadTokens || 0,
+ )
+
+ return {
+ type: "usage",
+ inputTokens: totalInputTokens,
+ outputTokens: totalOutputTokens,
+ cacheWriteTokens,
+ cacheReadTokens,
+ totalCost,
+ }
+ }
+
+ private resolveResponseId(responseId: string | undefined): void {
+ if (responseId) {
+ this.lastResponseId = responseId
+ }
+ // Resolve the promise so the next request can use this ID
+ if (this.responseIdResolver) {
+ this.responseIdResolver(responseId)
+ this.responseIdResolver = undefined
+ }
+ }
+
override async *createMessage(
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
@@ -81,8 +117,9 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
yield* this.handleReasonerMessage(model, id, systemPrompt, messages)
} else if (model.id.startsWith("o1")) {
yield* this.handleO1FamilyMessage(model, systemPrompt, messages)
- } else if (this.isGpt5Model(model.id)) {
- yield* this.handleGpt5Message(model, systemPrompt, messages)
+ } else if (this.isResponsesApiModel(model.id)) {
+ // Both GPT-5 and Codex Mini use the v1/responses endpoint
+ yield* this.handleResponsesApiMessage(model, systemPrompt, messages, metadata)
} else {
yield* this.handleDefaultModelMessage(model, systemPrompt, messages)
}
@@ -157,8 +194,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
...(reasoning && reasoning),
}
- // Add verbosity if supported (for future GPT-5 models)
- if (verbosity && model.id.startsWith("gpt-5")) {
+ // Add verbosity only if the model supports it
+ if (verbosity && model.info.supportsVerbosity) {
params.verbosity = verbosity
}
@@ -176,185 +213,935 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
)
}
- private async *handleGpt5Message(
+ private async *handleResponsesApiMessage(
model: OpenAiNativeModel,
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
+ metadata?: ApiHandlerCreateMessageMetadata,
): ApiStream {
- // GPT-5 uses the Responses API, not Chat Completions
- // We need to format the input as a single string combining system prompt and messages
- const formattedInput = this.formatInputForResponsesAPI(systemPrompt, messages)
+ // Prefer the official SDK Responses API with streaming; fall back to fetch-based SSE if needed.
+ const { verbosity } = this.getModel()
- // Get reasoning effort, supporting the new "minimal" option for GPT-5
+ // Both GPT-5 and Codex Mini use the same v1/responses endpoint format
+
+ // Resolve reasoning effort (supports "minimal" for GPT‑5)
const reasoningEffort = this.getGpt5ReasoningEffort(model)
- // Get verbosity from model settings, default to "medium" if not specified
- const verbosity = model.verbosity || "medium"
+ // Wait for any pending response ID from a previous request to be available
+ // This handles the race condition with fast nano model responses
+ let effectivePreviousResponseId = metadata?.previousResponseId
- // Prepare the request parameters for Responses API
- const params: GPT5ResponsesAPIParams = {
+ // Only allow fallback to pending/last response id when not explicitly suppressed
+ if (!metadata?.suppressPreviousResponseId) {
+ // If we have a pending response ID promise, wait for it to resolve
+ if (!effectivePreviousResponseId && this.responseIdPromise) {
+ try {
+ const resolvedId = await Promise.race([
+ this.responseIdPromise,
+ // Timeout after 100ms to avoid blocking too long
+ new Promise((resolve) => setTimeout(() => resolve(undefined), 100)),
+ ])
+ if (resolvedId) {
+ effectivePreviousResponseId = resolvedId
+ }
+ } catch {
+ // Non-fatal if promise fails
+ }
+ }
+
+ // Fall back to the last known response ID if still not available
+ if (!effectivePreviousResponseId) {
+ effectivePreviousResponseId = this.lastResponseId
+ }
+ }
+
+ // Format input and capture continuity id
+ const { formattedInput, previousResponseId } = this.prepareGpt5Input(systemPrompt, messages, metadata)
+ const requestPreviousResponseId = effectivePreviousResponseId ?? previousResponseId
+
+ // Create a new promise for this request's response ID
+ this.responseIdPromise = new Promise((resolve) => {
+ this.responseIdResolver = resolve
+ })
+
+ // Build a request body (also used for fallback)
+ // Ensure we explicitly pass max_output_tokens for GPT‑5 based on Roo's reserved model response calculation
+ // so requests do not default to very large limits (e.g., 120k).
+ interface Gpt5RequestBody {
+ model: string
+ input: string
+ stream: boolean
+ reasoning?: { effort: ReasoningEffortWithMinimal; summary?: "auto" }
+ text?: { verbosity: VerbosityLevel }
+ temperature?: number
+ max_output_tokens?: number
+ previous_response_id?: string
+ }
+
+ const requestBody: Gpt5RequestBody = {
model: model.id,
input: formattedInput,
+ stream: true,
...(reasoningEffort && {
reasoning: {
effort: reasoningEffort,
+ ...(this.options.enableGpt5ReasoningSummary ? { summary: "auto" as const } : {}),
},
}),
- text: {
- verbosity: verbosity,
- },
+ text: { verbosity: (verbosity || "medium") as VerbosityLevel },
+ temperature: this.options.modelTemperature ?? GPT5_DEFAULT_TEMPERATURE,
+ // Explicitly include the calculated max output tokens for GPT‑5.
+ // Use the per-request reserved output computed by Roo (params.maxTokens from getModelParams).
+ ...(model.maxTokens ? { max_output_tokens: model.maxTokens } : {}),
+ ...(requestPreviousResponseId && { previous_response_id: requestPreviousResponseId }),
}
- // Since the OpenAI SDK doesn't yet support the Responses API,
- // we'll make a direct HTTP request
- const response = await this.makeGpt5ResponsesAPIRequest(params, model)
+ try {
+ // Use the official SDK
+ const stream = (await (this.client as any).responses.create(requestBody)) as AsyncIterable
- yield* this.handleGpt5StreamResponse(response, model)
+ if (typeof (stream as any)[Symbol.asyncIterator] !== "function") {
+ throw new Error(
+ "OpenAI SDK did not return an AsyncIterable for Responses API streaming. Falling back to SSE.",
+ )
+ }
+
+ for await (const event of stream) {
+ for await (const outChunk of this.processGpt5Event(event, model)) {
+ yield outChunk
+ }
+ }
+ } catch (sdkErr: any) {
+ // Check if this is a 400 error about previous_response_id not found
+ const errorMessage = sdkErr?.message || sdkErr?.error?.message || ""
+ const is400Error = sdkErr?.status === 400 || sdkErr?.response?.status === 400
+ const isPreviousResponseError =
+ errorMessage.includes("Previous response") || errorMessage.includes("not found")
+
+ if (is400Error && requestBody.previous_response_id && isPreviousResponseError) {
+ // Log the error and retry without the previous_response_id
+ console.warn(
+ `[GPT-5] Previous response ID not found (${requestBody.previous_response_id}), retrying without it`,
+ )
+
+ // Remove the problematic previous_response_id and retry
+ const retryRequestBody = { ...requestBody }
+ delete retryRequestBody.previous_response_id
+
+ // Clear the stored lastResponseId to prevent using it again
+ this.lastResponseId = undefined
+
+ try {
+ // Retry with the SDK
+ const retryStream = (await (this.client as any).responses.create(
+ retryRequestBody,
+ )) as AsyncIterable
+
+ if (typeof (retryStream as any)[Symbol.asyncIterator] !== "function") {
+ // If SDK fails, fall back to SSE
+ yield* this.makeGpt5ResponsesAPIRequest(retryRequestBody, model, metadata)
+ return
+ }
+
+ for await (const event of retryStream) {
+ for await (const outChunk of this.processGpt5Event(event, model)) {
+ yield outChunk
+ }
+ }
+ return
+ } catch (retryErr) {
+ // If retry also fails, fall back to SSE
+ yield* this.makeGpt5ResponsesAPIRequest(retryRequestBody, model, metadata)
+ return
+ }
+ }
+
+ // For other errors, fallback to manual SSE via fetch
+ yield* this.makeGpt5ResponsesAPIRequest(requestBody, model, metadata)
+ }
}
private formatInputForResponsesAPI(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
- // Format the conversation for the Responses API's single input field
- let formattedInput = `System: ${systemPrompt}\n\n`
+ // Format the conversation for the Responses API input field
+ // Use Developer role format for GPT-5 (aligning with o1/o3 Developer role usage per GPT-5 Responses guidance)
+ // This ensures consistent instruction handling across reasoning models
+ let formattedInput = `Developer: ${systemPrompt}\n\n`
for (const message of messages) {
const role = message.role === "user" ? "User" : "Assistant"
- const content =
- typeof message.content === "string"
- ? message.content
- : message.content.map((c) => (c.type === "text" ? c.text : "[image]")).join(" ")
- formattedInput += `${role}: ${content}\n\n`
+
+ // Handle text content
+ if (typeof message.content === "string") {
+ formattedInput += `${role}: ${message.content}\n\n`
+ } else if (Array.isArray(message.content)) {
+ // Handle content blocks
+ const textContent = message.content
+ .filter((block) => block.type === "text")
+ .map((block) => (block as any).text)
+ .join("\n")
+ if (textContent) {
+ formattedInput += `${role}: ${textContent}\n\n`
+ }
+ }
}
return formattedInput.trim()
}
+ private formatSingleMessageForResponsesAPI(message: Anthropic.Messages.MessageParam): string {
+ // Format a single message for the Responses API when using previous_response_id
+ const role = message.role === "user" ? "User" : "Assistant"
+
+ // Handle text content
+ if (typeof message.content === "string") {
+ return `${role}: ${message.content}`
+ } else if (Array.isArray(message.content)) {
+ // Handle content blocks
+ const textContent = message.content
+ .filter((block) => block.type === "text")
+ .map((block) => (block as any).text)
+ .join("\n")
+ if (textContent) {
+ return `${role}: ${textContent}`
+ }
+ }
+
+ return ""
+ }
+
+ private async *makeGpt5ResponsesAPIRequest(
+ requestBody: any,
+ model: OpenAiNativeModel,
+ metadata?: ApiHandlerCreateMessageMetadata,
+ ): ApiStream {
+ const apiKey = this.options.openAiNativeApiKey ?? "not-provided"
+ const baseUrl = this.options.openAiNativeBaseUrl || "https://api.openai.com"
+ const url = `${baseUrl}/v1/responses`
+
+ try {
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${apiKey}`,
+ Accept: "text/event-stream",
+ },
+ body: JSON.stringify(requestBody),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+
+ let errorMessage = `GPT-5 API request failed (${response.status})`
+ let errorDetails = ""
+
+ // Try to parse error as JSON for better error messages
+ try {
+ const errorJson = JSON.parse(errorText)
+ if (errorJson.error?.message) {
+ errorDetails = errorJson.error.message
+ } else if (errorJson.message) {
+ errorDetails = errorJson.message
+ } else {
+ errorDetails = errorText
+ }
+ } catch {
+ // If not JSON, use the raw text
+ errorDetails = errorText
+ }
+
+ // Check if this is a 400 error about previous_response_id not found
+ const isPreviousResponseError =
+ errorDetails.includes("Previous response") || errorDetails.includes("not found")
+
+ if (response.status === 400 && requestBody.previous_response_id && isPreviousResponseError) {
+ // Log the error and retry without the previous_response_id
+ console.warn(
+ `[GPT-5 SSE] Previous response ID not found (${requestBody.previous_response_id}), retrying without it`,
+ )
+
+ // Remove the problematic previous_response_id and retry
+ const retryRequestBody = { ...requestBody }
+ delete retryRequestBody.previous_response_id
+
+ // Clear the stored lastResponseId to prevent using it again
+ this.lastResponseId = undefined
+ // Resolve the promise once to unblock any waiting requests
+ this.resolveResponseId(undefined)
+
+ // Retry the request without the previous_response_id
+ const retryResponse = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${apiKey}`,
+ Accept: "text/event-stream",
+ },
+ body: JSON.stringify(retryRequestBody),
+ })
+
+ if (!retryResponse.ok) {
+ // If retry also fails, throw the original error
+ throw new Error(`GPT-5 API retry failed (${retryResponse.status})`)
+ }
+
+ if (!retryResponse.body) {
+ throw new Error("GPT-5 Responses API error: No response body from retry request")
+ }
+
+ // Handle the successful retry response
+ yield* this.handleGpt5StreamResponse(retryResponse.body, model)
+ return
+ }
+
+ // Provide user-friendly error messages based on status code
+ switch (response.status) {
+ case 400:
+ errorMessage = "Invalid request to GPT-5 API. Please check your input parameters."
+ break
+ case 401:
+ errorMessage = "Authentication failed. Please check your OpenAI API key."
+ break
+ case 403:
+ errorMessage = "Access denied. Your API key may not have access to GPT-5 models."
+ break
+ case 404:
+ errorMessage =
+ "GPT-5 API endpoint not found. The model may not be available yet or requires a different configuration."
+ break
+ case 429:
+ errorMessage = "Rate limit exceeded. Please try again later."
+ break
+ case 500:
+ case 502:
+ case 503:
+ errorMessage = "OpenAI service error. Please try again later."
+ break
+ default:
+ errorMessage = `GPT-5 API error (${response.status})`
+ }
+
+ // Append details if available
+ if (errorDetails) {
+ errorMessage += ` - ${errorDetails}`
+ }
+
+ throw new Error(errorMessage)
+ }
+
+ if (!response.body) {
+ throw new Error("GPT-5 Responses API error: No response body")
+ }
+
+ // Handle streaming response
+ yield* this.handleGpt5StreamResponse(response.body, model)
+ } catch (error) {
+ if (error instanceof Error) {
+ // Re-throw with the original error message if it's already formatted
+ if (error.message.includes("GPT-5")) {
+ throw error
+ }
+ // Otherwise, wrap it with context
+ throw new Error(`Failed to connect to GPT-5 API: ${error.message}`)
+ }
+ // Handle non-Error objects
+ throw new Error(`Unexpected error connecting to GPT-5 API`)
+ }
+ }
+
+ /**
+ * Prepares the input and conversation continuity parameters for a GPT-5 API call.
+ *
+ * - If a `previousResponseId` is available (either from metadata or the handler's state),
+ * it formats only the most recent user message for the input and returns the response ID
+ * to maintain conversation context.
+ * - Otherwise, it formats the entire conversation history (system prompt + messages) for the input.
+ *
+ * @returns An object containing the formatted input string and the previous response ID (if used).
+ */
+ private prepareGpt5Input(
+ systemPrompt: string,
+ messages: Anthropic.Messages.MessageParam[],
+ metadata?: ApiHandlerCreateMessageMetadata,
+ ): { formattedInput: string; previousResponseId?: string } {
+ // Respect explicit suppression signal for continuity (e.g. immediately after condense)
+ const isFirstMessage = messages.length === 1 && messages[0].role === "user"
+ const allowFallback = !metadata?.suppressPreviousResponseId
+
+ const previousResponseId =
+ metadata?.previousResponseId ?? (allowFallback && !isFirstMessage ? this.lastResponseId : undefined)
+
+ if (previousResponseId) {
+ const lastUserMessage = [...messages].reverse().find((msg) => msg.role === "user")
+ const formattedInput = lastUserMessage ? this.formatSingleMessageForResponsesAPI(lastUserMessage) : ""
+ return { formattedInput, previousResponseId }
+ } else {
+ const formattedInput = this.formatInputForResponsesAPI(systemPrompt, messages)
+ return { formattedInput }
+ }
+ }
+
+ /**
+ * Handles the streaming response from the GPT-5 Responses API.
+ *
+ * This function iterates through the Server-Sent Events (SSE) stream, parses each event,
+ * and yields structured data chunks (`ApiStream`). It handles a wide variety of event types,
+ * including text deltas, reasoning, usage data, and various status/tool events.
+ *
+ * The following event types are intentionally ignored as they are not currently consumed
+ * by the client application:
+ * - Audio events (`response.audio.*`)
+ * - Most tool call events (e.g., `response.function_call_arguments.*`, `response.mcp_call.*`, etc.)
+ * as the client does not yet support rendering these tool interactions.
+ * - Status events (`response.created`, `response.in_progress`, etc.) as they are informational
+ * and do not affect the final output.
+ */
+ private async *handleGpt5StreamResponse(body: ReadableStream, model: OpenAiNativeModel): ApiStream {
+ const reader = body.getReader()
+ const decoder = new TextDecoder()
+ let buffer = ""
+ let hasContent = false
+ let totalInputTokens = 0
+ let totalOutputTokens = 0
+
+ try {
+ while (true) {
+ const { done, value } = await reader.read()
+ if (done) break
+
+ buffer += decoder.decode(value, { stream: true })
+ const lines = buffer.split("\n")
+ buffer = lines.pop() || ""
+
+ for (const line of lines) {
+ if (line.startsWith("data: ")) {
+ const data = line.slice(6).trim()
+ if (data === "[DONE]") {
+ continue
+ }
+
+ try {
+ const parsed = JSON.parse(data)
+
+ // Store response ID for conversation continuity
+ if (parsed.response?.id) {
+ this.resolveResponseId(parsed.response.id)
+ }
+
+ // Delegate standard event types to the shared processor to avoid duplication
+ if (parsed?.type && this.gpt5CoreHandledTypes.has(parsed.type)) {
+ for await (const outChunk of this.processGpt5Event(parsed, model)) {
+ // Track whether we've emitted any content so fallback handling can decide appropriately
+ if (outChunk.type === "text" || outChunk.type === "reasoning") {
+ hasContent = true
+ }
+ yield outChunk
+ }
+ continue
+ }
+
+ // Check if this is a complete response (non-streaming format)
+ if (parsed.response && parsed.response.output && Array.isArray(parsed.response.output)) {
+ // Handle complete response in the initial event
+ for (const outputItem of parsed.response.output) {
+ if (outputItem.type === "text" && outputItem.content) {
+ for (const content of outputItem.content) {
+ if (content.type === "text" && content.text) {
+ hasContent = true
+ yield {
+ type: "text",
+ text: content.text,
+ }
+ }
+ }
+ }
+ // Additionally handle reasoning summaries if present (non-streaming summary output)
+ if (outputItem.type === "reasoning" && Array.isArray(outputItem.summary)) {
+ for (const summary of outputItem.summary) {
+ if (summary?.type === "summary_text" && typeof summary.text === "string") {
+ hasContent = true
+ yield {
+ type: "reasoning",
+ text: summary.text,
+ }
+ }
+ }
+ }
+ }
+ // Check for usage in the complete response
+ if (parsed.response.usage) {
+ const usageData = this.normalizeGpt5Usage(parsed.response.usage, model)
+ if (usageData) {
+ yield usageData
+ }
+ }
+ }
+ // Handle streaming delta events for text content
+ else if (
+ parsed.type === "response.text.delta" ||
+ parsed.type === "response.output_text.delta"
+ ) {
+ // Primary streaming event for text deltas
+ if (parsed.delta) {
+ hasContent = true
+ yield {
+ type: "text",
+ text: parsed.delta,
+ }
+ }
+ } else if (
+ parsed.type === "response.text.done" ||
+ parsed.type === "response.output_text.done"
+ ) {
+ // Text streaming completed - final text already streamed via deltas
+ }
+ // Handle reasoning delta events
+ else if (
+ parsed.type === "response.reasoning.delta" ||
+ parsed.type === "response.reasoning_text.delta"
+ ) {
+ // Streaming reasoning content
+ if (parsed.delta) {
+ hasContent = true
+ yield {
+ type: "reasoning",
+ text: parsed.delta,
+ }
+ }
+ } else if (
+ parsed.type === "response.reasoning.done" ||
+ parsed.type === "response.reasoning_text.done"
+ ) {
+ // Reasoning streaming completed
+ }
+ // Handle reasoning summary events
+ else if (
+ parsed.type === "response.reasoning_summary.delta" ||
+ parsed.type === "response.reasoning_summary_text.delta"
+ ) {
+ // Streaming reasoning summary
+ if (parsed.delta) {
+ hasContent = true
+ yield {
+ type: "reasoning",
+ text: parsed.delta,
+ }
+ }
+ } else if (
+ parsed.type === "response.reasoning_summary.done" ||
+ parsed.type === "response.reasoning_summary_text.done"
+ ) {
+ // Reasoning summary completed
+ }
+ // Handle refusal delta events
+ else if (parsed.type === "response.refusal.delta") {
+ // Model is refusing to answer
+ if (parsed.delta) {
+ hasContent = true
+ yield {
+ type: "text",
+ text: `[Refusal] ${parsed.delta}`,
+ }
+ }
+ } else if (parsed.type === "response.refusal.done") {
+ // Refusal completed
+ }
+ // Handle audio delta events (for multimodal responses)
+ else if (parsed.type === "response.audio.delta") {
+ // Audio streaming - we'll skip for now as we focus on text
+ // Could be handled in future for voice responses
+ } else if (parsed.type === "response.audio.done") {
+ // Audio completed
+ }
+ // Handle audio transcript delta events
+ else if (parsed.type === "response.audio_transcript.delta") {
+ // Audio transcript streaming
+ if (parsed.delta) {
+ hasContent = true
+ yield {
+ type: "text",
+ text: parsed.delta,
+ }
+ }
+ } else if (parsed.type === "response.audio_transcript.done") {
+ // Audio transcript completed
+ }
+ // Handle content part events (for structured content)
+ else if (parsed.type === "response.content_part.added") {
+ // New content part added - could be text, image, etc.
+ if (parsed.part?.type === "text" && parsed.part.text) {
+ hasContent = true
+ yield {
+ type: "text",
+ text: parsed.part.text,
+ }
+ }
+ } else if (parsed.type === "response.content_part.done") {
+ // Content part completed
+ }
+ // Handle output item events (alternative format)
+ else if (parsed.type === "response.output_item.added") {
+ // This is where the actual content comes through in some test cases
+ if (parsed.item) {
+ if (parsed.item.type === "text" && parsed.item.text) {
+ hasContent = true
+ yield { type: "text", text: parsed.item.text }
+ } else if (parsed.item.type === "reasoning" && parsed.item.text) {
+ hasContent = true
+ yield { type: "reasoning", text: parsed.item.text }
+ } else if (parsed.item.type === "message" && parsed.item.content) {
+ // Handle message type items
+ for (const content of parsed.item.content) {
+ if (content.type === "text" && content.text) {
+ hasContent = true
+ yield { type: "text", text: content.text }
+ }
+ }
+ }
+ }
+ } else if (parsed.type === "response.output_item.done") {
+ // Output item completed
+ }
+ // Handle function/tool call events
+ else if (parsed.type === "response.function_call_arguments.delta") {
+ // Function call arguments streaming
+ // We could yield this as a special type if needed for tool usage
+ } else if (parsed.type === "response.function_call_arguments.done") {
+ // Function call completed
+ }
+ // Handle MCP (Model Context Protocol) tool events
+ else if (parsed.type === "response.mcp_call_arguments.delta") {
+ // MCP tool call arguments streaming
+ } else if (parsed.type === "response.mcp_call_arguments.done") {
+ // MCP tool call completed
+ } else if (parsed.type === "response.mcp_call.in_progress") {
+ // MCP tool call in progress
+ } else if (
+ parsed.type === "response.mcp_call.completed" ||
+ parsed.type === "response.mcp_call.failed"
+ ) {
+ // MCP tool call status events
+ } else if (parsed.type === "response.mcp_list_tools.in_progress") {
+ // MCP list tools in progress
+ } else if (
+ parsed.type === "response.mcp_list_tools.completed" ||
+ parsed.type === "response.mcp_list_tools.failed"
+ ) {
+ // MCP list tools status events
+ }
+ // Handle web search events
+ else if (parsed.type === "response.web_search_call.searching") {
+ // Web search in progress
+ } else if (parsed.type === "response.web_search_call.in_progress") {
+ // Processing web search results
+ } else if (parsed.type === "response.web_search_call.completed") {
+ // Web search completed
+ }
+ // Handle code interpreter events
+ else if (parsed.type === "response.code_interpreter_call_code.delta") {
+ // Code interpreter code streaming
+ if (parsed.delta) {
+ // Could yield as a special code type if needed
+ }
+ } else if (parsed.type === "response.code_interpreter_call_code.done") {
+ // Code interpreter code completed
+ } else if (parsed.type === "response.code_interpreter_call.interpreting") {
+ // Code interpreter running
+ } else if (parsed.type === "response.code_interpreter_call.in_progress") {
+ // Code execution in progress
+ } else if (parsed.type === "response.code_interpreter_call.completed") {
+ // Code interpreter completed
+ }
+ // Handle file search events
+ else if (parsed.type === "response.file_search_call.searching") {
+ // File search in progress
+ } else if (parsed.type === "response.file_search_call.in_progress") {
+ // Processing file search results
+ } else if (parsed.type === "response.file_search_call.completed") {
+ // File search completed
+ }
+ // Handle image generation events
+ else if (parsed.type === "response.image_gen_call.generating") {
+ // Image generation in progress
+ } else if (parsed.type === "response.image_gen_call.in_progress") {
+ // Processing image generation
+ } else if (parsed.type === "response.image_gen_call.partial_image") {
+ // Image partially generated
+ } else if (parsed.type === "response.image_gen_call.completed") {
+ // Image generation completed
+ }
+ // Handle computer use events
+ else if (
+ parsed.type === "response.computer_tool_call.output_item" ||
+ parsed.type === "response.computer_tool_call.output_screenshot"
+ ) {
+ // Computer use tool events
+ }
+ // Handle annotation events
+ else if (
+ parsed.type === "response.output_text_annotation.added" ||
+ parsed.type === "response.text_annotation.added"
+ ) {
+ // Text annotation events - could be citations, references, etc.
+ }
+ // Handle error events
+ else if (parsed.type === "response.error" || parsed.type === "error") {
+ // Error event from the API
+ if (parsed.error || parsed.message) {
+ throw new Error(
+ `Responses API error: ${parsed.error?.message || parsed.message || "Unknown error"}`,
+ )
+ }
+ }
+ // Handle incomplete event
+ else if (parsed.type === "response.incomplete") {
+ // Response was incomplete - might need to handle specially
+ }
+ // Handle queued event
+ else if (parsed.type === "response.queued") {
+ // Response is queued
+ }
+ // Handle in_progress event
+ else if (parsed.type === "response.in_progress") {
+ // Response is being processed
+ }
+ // Handle failed event
+ else if (parsed.type === "response.failed") {
+ // Response failed
+ if (parsed.error || parsed.message) {
+ throw new Error(
+ `GPT-5 response failed: ${parsed.error?.message || parsed.message || "Unknown failure"}`,
+ )
+ }
+ } else if (parsed.type === "response.completed" || parsed.type === "response.done") {
+ // Store response ID for conversation continuity
+ if (parsed.response?.id) {
+ this.resolveResponseId(parsed.response.id)
+ }
+
+ // Check if the done event contains the complete output (as a fallback)
+ if (
+ !hasContent &&
+ parsed.response &&
+ parsed.response.output &&
+ Array.isArray(parsed.response.output)
+ ) {
+ for (const outputItem of parsed.response.output) {
+ if (outputItem.type === "message" && outputItem.content) {
+ for (const content of outputItem.content) {
+ if (content.type === "output_text" && content.text) {
+ hasContent = true
+ yield {
+ type: "text",
+ text: content.text,
+ }
+ }
+ }
+ }
+ // Also surface reasoning summaries if present in the final output
+ if (outputItem.type === "reasoning" && Array.isArray(outputItem.summary)) {
+ for (const summary of outputItem.summary) {
+ if (
+ summary?.type === "summary_text" &&
+ typeof summary.text === "string"
+ ) {
+ hasContent = true
+ yield {
+ type: "reasoning",
+ text: summary.text,
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Usage for done/completed is already handled by processGpt5Event in SDK path.
+ // For SSE path, usage often arrives separately; avoid double-emitting here.
+ }
+ // These are structural or status events, we can just log them at a lower level or ignore.
+ else if (
+ parsed.type === "response.created" ||
+ parsed.type === "response.in_progress" ||
+ parsed.type === "response.output_item.done" ||
+ parsed.type === "response.content_part.added" ||
+ parsed.type === "response.content_part.done"
+ ) {
+ // Status events - no action needed
+ }
+ // Fallback for older formats or unexpected responses
+ else if (parsed.choices?.[0]?.delta?.content) {
+ hasContent = true
+ yield {
+ type: "text",
+ text: parsed.choices[0].delta.content,
+ }
+ }
+ // Additional fallback: some events place text under 'item.text' even if type isn't matched above
+ else if (
+ parsed.item &&
+ typeof parsed.item.text === "string" &&
+ parsed.item.text.length > 0
+ ) {
+ hasContent = true
+ yield {
+ type: "text",
+ text: parsed.item.text,
+ }
+ } else if (parsed.usage) {
+ // Handle usage if it arrives in a separate, non-completed event
+ const usageData = this.normalizeGpt5Usage(parsed.usage, model)
+ if (usageData) {
+ yield usageData
+ }
+ }
+ } catch (e) {
+ // Only ignore JSON parsing errors, re-throw actual API errors
+ if (!(e instanceof SyntaxError)) {
+ throw e
+ }
+ }
+ }
+ // Also try to parse non-SSE formatted lines
+ else if (line.trim() && !line.startsWith(":")) {
+ try {
+ const parsed = JSON.parse(line)
+
+ // Try to extract content from various possible locations
+ if (parsed.content || parsed.text || parsed.message) {
+ hasContent = true
+ yield {
+ type: "text",
+ text: parsed.content || parsed.text || parsed.message,
+ }
+ }
+ } catch {
+ // Not JSON, might be plain text - ignore
+ }
+ }
+ }
+ }
+
+ // If we didn't get any content, don't throw - the API might have returned an empty response
+ // This can happen in certain edge cases and shouldn't break the flow
+ } catch (error) {
+ if (error instanceof Error) {
+ throw new Error(`Error processing GPT-5 response stream: ${error.message}`)
+ }
+ throw new Error("Unexpected error processing GPT-5 response stream")
+ } finally {
+ reader.releaseLock()
+ }
+ }
+
+ /**
+ * Shared processor for GPT‑5 Responses API events.
+ * Used by both the official SDK streaming path and (optionally) by the SSE fallback.
+ */
+ private async *processGpt5Event(event: any, model: OpenAiNativeModel): ApiStream {
+ // Persist response id for conversation continuity when available
+ if (event?.response?.id) {
+ this.resolveResponseId(event.response.id)
+ }
+
+ // Handle known streaming text deltas
+ if (event?.type === "response.text.delta" || event?.type === "response.output_text.delta") {
+ if (event?.delta) {
+ yield { type: "text", text: event.delta }
+ }
+ return
+ }
+
+ // Handle reasoning deltas (including summary variants)
+ if (
+ event?.type === "response.reasoning.delta" ||
+ event?.type === "response.reasoning_text.delta" ||
+ event?.type === "response.reasoning_summary.delta" ||
+ event?.type === "response.reasoning_summary_text.delta"
+ ) {
+ if (event?.delta) {
+ yield { type: "reasoning", text: event.delta }
+ }
+ return
+ }
+
+ // Handle refusal deltas
+ if (event?.type === "response.refusal.delta") {
+ if (event?.delta) {
+ yield { type: "text", text: `[Refusal] ${event.delta}` }
+ }
+ return
+ }
+
+ // Handle output item additions (SDK or Responses API alternative format)
+ if (event?.type === "response.output_item.added") {
+ const item = event?.item
+ if (item) {
+ if (item.type === "text" && item.text) {
+ yield { type: "text", text: item.text }
+ } else if (item.type === "reasoning" && item.text) {
+ yield { type: "reasoning", text: item.text }
+ } else if (item.type === "message" && Array.isArray(item.content)) {
+ for (const content of item.content) {
+ // Some implementations send 'text'; others send 'output_text'
+ if ((content?.type === "text" || content?.type === "output_text") && content?.text) {
+ yield { type: "text", text: content.text }
+ }
+ }
+ }
+ }
+ return
+ }
+
+ // Completion events that may carry usage
+ if (event?.type === "response.done" || event?.type === "response.completed") {
+ const usage = event?.response?.usage || event?.usage || undefined
+ const usageData = this.normalizeGpt5Usage(usage, model)
+ if (usageData) {
+ yield usageData
+ }
+ return
+ }
+
+ // Fallbacks for older formats or unexpected objects
+ if (event?.choices?.[0]?.delta?.content) {
+ yield { type: "text", text: event.choices[0].delta.content }
+ return
+ }
+
+ if (event?.usage) {
+ const usageData = this.normalizeGpt5Usage(event.usage, model)
+ if (usageData) {
+ yield usageData
+ }
+ }
+ }
+
private getGpt5ReasoningEffort(model: OpenAiNativeModel): ReasoningEffortWithMinimal | undefined {
- const { reasoning } = model
+ const { reasoning, info } = model
// Check if reasoning effort is configured
if (reasoning && "reasoning_effort" in reasoning) {
- const effort = reasoning.reasoning_effort
- // Support the new "minimal" effort level for GPT-5
- if (effort === "low" || effort === "medium" || effort === "high") {
- return effort
+ const effort = reasoning.reasoning_effort as string
+ // Support all effort levels including "minimal" for GPT-5
+ if (effort === "minimal" || effort === "low" || effort === "medium" || effort === "high") {
+ return effort as ReasoningEffortWithMinimal
}
}
- // Default to "minimal" for GPT-5 models when not specified
- // This provides fastest time-to-first-token as per documentation
- return "minimal"
- }
-
- private async makeGpt5ResponsesAPIRequest(
- params: GPT5ResponsesAPIParams,
- model: OpenAiNativeModel,
- ): Promise> {
- // The OpenAI SDK doesn't have direct support for the Responses API yet,
- // but we can access it through the underlying client request method if available.
- // For now, we'll use the Chat Completions API with GPT-5 specific formatting
- // to maintain compatibility while the Responses API SDK support is being added.
-
- // Convert Responses API params to Chat Completions format
- // GPT-5 models use "developer" role for system messages
- const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [{ role: "developer", content: params.input }]
-
- // Build the request parameters
- const requestParams: any = {
- model: params.model,
- messages,
- stream: true,
- stream_options: { include_usage: true },
- }
-
- // Add reasoning effort if specified (supporting "minimal" for GPT-5)
- if (params.reasoning?.effort) {
- if (params.reasoning.effort === "minimal") {
- // For minimal effort, we pass "minimal" as the reasoning_effort
- requestParams.reasoning_effort = "minimal"
- } else {
- requestParams.reasoning_effort = params.reasoning.effort
- }
- }
-
- // Add verbosity control for GPT-5 models
- // According to the docs, Chat Completions API also supports verbosity parameter
- if (params.text?.verbosity) {
- requestParams.verbosity = params.text.verbosity
- }
-
- const stream = (await this.client.chat.completions.create(
- requestParams,
- )) as unknown as AsyncIterable
-
- // Convert the stream to GPT-5 response format
- return this.convertChatStreamToGpt5Format(stream)
- }
-
- private async *convertChatStreamToGpt5Format(
- stream: AsyncIterable,
- ): AsyncIterable {
- for await (const chunk of stream) {
- const delta = chunk.choices[0]?.delta
-
- if (delta?.content) {
- yield {
- type: "text",
- text: delta.content,
- }
- }
-
- if (chunk.usage) {
- yield {
- type: "usage",
- usage: {
- input_tokens: chunk.usage.prompt_tokens || 0,
- output_tokens: chunk.usage.completion_tokens || 0,
- total_tokens: chunk.usage.total_tokens || 0,
- },
- }
- }
- }
- }
-
- private async *handleGpt5StreamResponse(
- stream: AsyncIterable,
- model: OpenAiNativeModel,
- ): ApiStream {
- for await (const chunk of stream) {
- if (chunk.type === "text" && chunk.text) {
- yield {
- type: "text",
- text: chunk.text,
- }
- } else if (chunk.type === "usage" && chunk.usage) {
- const inputTokens = chunk.usage.input_tokens
- const outputTokens = chunk.usage.output_tokens
- const cacheReadTokens = 0
- const cacheWriteTokens = 0
- const totalCost = calculateApiCostOpenAI(
- model.info,
- inputTokens,
- outputTokens,
- cacheWriteTokens,
- cacheReadTokens,
- )
-
- yield {
- type: "usage",
- inputTokens,
- outputTokens,
- cacheWriteTokens,
- cacheReadTokens,
- totalCost,
- }
- }
- }
+ // Centralize default: use the model's default from types if available; otherwise undefined
+ return info.reasoningEffort as ReasoningEffortWithMinimal | undefined
}
private isGpt5Model(modelId: string): boolean {
return modelId.startsWith("gpt-5")
}
+ private isResponsesApiModel(modelId: string): boolean {
+ // Both GPT-5 and Codex Mini use the v1/responses endpoint
+ return modelId.startsWith("gpt-5") || modelId === "codex-mini-latest"
+ }
+
private async *handleStreamResponse(
stream: AsyncIterable,
model: OpenAiNativeModel,
@@ -376,16 +1163,28 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
}
private async *yieldUsage(info: ModelInfo, usage: OpenAI.Completions.CompletionUsage | undefined): ApiStream {
- const inputTokens = usage?.prompt_tokens || 0 // sum of cache hits and misses
+ const inputTokens = usage?.prompt_tokens || 0
const outputTokens = usage?.completion_tokens || 0
- const cacheReadTokens = usage?.prompt_tokens_details?.cached_tokens || 0
- const cacheWriteTokens = 0
- const totalCost = calculateApiCostOpenAI(info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
- const nonCachedInputTokens = Math.max(0, inputTokens - cacheReadTokens - cacheWriteTokens)
+
+ // Extract cache tokens from prompt_tokens_details
+ // According to OpenAI API, cached_tokens represents tokens read from cache
+ const cacheReadTokens = usage?.prompt_tokens_details?.cached_tokens || undefined
+
+ // Cache write tokens are not typically reported in the standard streaming response
+ // They would be in cache_creation_input_tokens if available
+ const cacheWriteTokens = (usage as any)?.cache_creation_input_tokens || undefined
+
+ const totalCost = calculateApiCostOpenAI(
+ info,
+ inputTokens,
+ outputTokens,
+ cacheWriteTokens || 0,
+ cacheReadTokens || 0,
+ )
yield {
type: "usage",
- inputTokens: nonCachedInputTokens,
+ inputTokens: inputTokens,
outputTokens: outputTokens,
cacheWriteTokens: cacheWriteTokens,
cacheReadTokens: cacheReadTokens,
@@ -406,15 +1205,17 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
modelId: id,
model: info,
settings: this.options,
- defaultTemperature: OPENAI_NATIVE_DEFAULT_TEMPERATURE,
+ defaultTemperature: this.isGpt5Model(id) ? GPT5_DEFAULT_TEMPERATURE : OPENAI_NATIVE_DEFAULT_TEMPERATURE,
})
- // For GPT-5 models, ensure we support minimal reasoning effort
- if (this.isGpt5Model(id) && params.reasoning) {
- // Allow "minimal" effort for GPT-5 models
- const effort = this.options.reasoningEffort
- if (effort === "low" || effort === "medium" || effort === "high") {
- params.reasoning.reasoning_effort = effort
+ // For models using the Responses API (GPT-5 and Codex Mini), ensure we support reasoning effort
+ if (this.isResponsesApiModel(id)) {
+ const effort =
+ (this.options.reasoningEffort as ReasoningEffortWithMinimal | undefined) ??
+ (info.reasoningEffort as ReasoningEffortWithMinimal | undefined)
+
+ if (effort) {
+ ;(params.reasoning as any) = { reasoning_effort: effort }
}
}
@@ -423,25 +1224,50 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
return { id: id.startsWith("o3-mini") ? "o3-mini" : id, info, ...params, verbosity: params.verbosity }
}
+ /**
+ * Gets the last GPT-5 response ID captured from the Responses API stream.
+ * Used for maintaining conversation continuity across requests.
+ * @returns The response ID, or undefined if not available yet
+ */
+ getLastResponseId(): string | undefined {
+ return this.lastResponseId
+ }
+
+ /**
+ * Sets the last GPT-5 response ID for conversation continuity.
+ * Typically only used in tests or special flows.
+ * @param responseId The GPT-5 response ID to store
+ */
+ setResponseId(responseId: string): void {
+ this.lastResponseId = responseId
+ }
+
async completePrompt(prompt: string): Promise {
try {
const { id, temperature, reasoning, verbosity } = this.getModel()
+ const isResponsesApi = this.isResponsesApiModel(id)
- const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming & {
- verbosity?: VerbosityLevel
- } = {
+ if (isResponsesApi) {
+ // Models that use the Responses API (GPT-5 and Codex Mini) don't support non-streaming completion
+ throw new Error(`completePrompt is not supported for ${id}. Use createMessage (Responses API) instead.`)
+ }
+
+ const params: any = {
model: id,
messages: [{ role: "user", content: prompt }],
- temperature,
- ...(reasoning && reasoning),
}
- // Add verbosity for GPT-5 models
- if (this.isGpt5Model(id) && verbosity) {
- params.verbosity = verbosity
+ // Add temperature if supported
+ if (temperature !== undefined) {
+ params.temperature = temperature
}
- const response = await this.client.chat.completions.create(params as any)
+ // Add reasoning parameters for models that support them
+ if (reasoning) {
+ Object.assign(params, reasoning)
+ }
+
+ const response = await this.client.chat.completions.create(params)
return response.choices[0]?.message.content || ""
} catch (error) {
if (error instanceof Error) {
diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts
index 85abcf1a69..939816480a 100644
--- a/src/api/providers/openai.ts
+++ b/src/api/providers/openai.ts
@@ -23,6 +23,7 @@ import { getModelParams } from "../transform/model-params"
import { DEFAULT_HEADERS } from "./constants"
import { BaseProvider } from "./base-provider"
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
+import { getApiRequestTimeout } from "./utils/timeout-config"
// TODO: Rename this to OpenAICompatibleHandler. Also, I think the
// `OpenAINativeHandler` can subclass from this, since it's obviously
@@ -46,6 +47,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
...(this.options.openAiHeaders || {}),
}
+ const timeout = getApiRequestTimeout()
+
if (isAzureAiInference) {
// Azure AI Inference Service (e.g., for DeepSeek) uses a different path structure
this.client = new OpenAI({
@@ -53,6 +56,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
apiKey,
defaultHeaders: headers,
defaultQuery: { "api-version": this.options.azureApiVersion || "2024-05-01-preview" },
+ timeout,
})
} else if (isAzureOpenAi) {
// Azure API shape slightly differs from the core API shape:
@@ -62,12 +66,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
apiKey,
apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion,
defaultHeaders: headers,
+ timeout,
})
} else {
this.client = new OpenAI({
baseURL,
apiKey,
defaultHeaders: headers,
+ timeout,
})
}
}
@@ -305,7 +311,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
],
stream: true,
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
- reasoning_effort: modelInfo.reasoningEffort,
+ reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined,
temperature: undefined,
}
@@ -330,7 +336,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
},
...convertToOpenAiMessages(messages),
],
- reasoning_effort: modelInfo.reasoningEffort,
+ reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined,
temperature: undefined,
}
diff --git a/src/api/providers/requesty.ts b/src/api/providers/requesty.ts
index 8af0b9aa42..771c695961 100644
--- a/src/api/providers/requesty.ts
+++ b/src/api/providers/requesty.ts
@@ -47,7 +47,7 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan
this.options = options
this.client = new OpenAI({
- baseURL: "https://router.requesty.ai/v1",
+ baseURL: options.requestyBaseUrl || "https://router.requesty.ai/v1",
apiKey: this.options.requestyApiKey ?? "not-provided",
defaultHeaders: DEFAULT_HEADERS,
})
@@ -116,7 +116,7 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan
model,
max_tokens,
temperature,
- ...(reasoning_effort && { reasoning_effort }),
+ ...(reasoning_effort && reasoning_effort !== "minimal" && { reasoning_effort }),
...(thinking && { thinking }),
stream: true,
stream_options: { include_usage: true },
diff --git a/src/api/providers/utils/__tests__/timeout-config.spec.ts b/src/api/providers/utils/__tests__/timeout-config.spec.ts
new file mode 100644
index 0000000000..f74e14afad
--- /dev/null
+++ b/src/api/providers/utils/__tests__/timeout-config.spec.ts
@@ -0,0 +1,99 @@
+// npx vitest run api/providers/utils/__tests__/timeout-config.spec.ts
+
+import { getApiRequestTimeout } from "../timeout-config"
+import * as vscode from "vscode"
+
+// Mock vscode
+vitest.mock("vscode", () => ({
+ workspace: {
+ getConfiguration: vitest.fn().mockReturnValue({
+ get: vitest.fn(),
+ }),
+ },
+}))
+
+describe("getApiRequestTimeout", () => {
+ let mockGetConfig: any
+
+ beforeEach(() => {
+ vitest.clearAllMocks()
+ mockGetConfig = vitest.fn()
+ ;(vscode.workspace.getConfiguration as any).mockReturnValue({
+ get: mockGetConfig,
+ })
+ })
+
+ it("should return default timeout of 600000ms when no configuration is set", () => {
+ mockGetConfig.mockReturnValue(600)
+
+ const timeout = getApiRequestTimeout()
+
+ expect(vscode.workspace.getConfiguration).toHaveBeenCalledWith("roo-cline")
+ expect(mockGetConfig).toHaveBeenCalledWith("apiRequestTimeout", 600)
+ expect(timeout).toBe(600000) // 600 seconds in milliseconds
+ })
+
+ it("should return custom timeout in milliseconds", () => {
+ mockGetConfig.mockReturnValue(1200) // 20 minutes
+
+ const timeout = getApiRequestTimeout()
+
+ expect(timeout).toBe(1200000) // 1200 seconds in milliseconds
+ })
+
+ it("should handle zero timeout (no timeout)", () => {
+ mockGetConfig.mockReturnValue(0)
+
+ const timeout = getApiRequestTimeout()
+
+ expect(timeout).toBe(0) // No timeout
+ })
+
+ it("should handle negative values by clamping to 0", () => {
+ mockGetConfig.mockReturnValue(-100)
+
+ const timeout = getApiRequestTimeout()
+
+ expect(timeout).toBe(0) // Negative values should be clamped to 0
+ })
+
+ it("should handle null by using default", () => {
+ mockGetConfig.mockReturnValue(null)
+
+ const timeout = getApiRequestTimeout()
+
+ expect(timeout).toBe(600000) // Should fall back to default 600 seconds
+ })
+
+ it("should handle undefined by using default", () => {
+ mockGetConfig.mockReturnValue(undefined)
+
+ const timeout = getApiRequestTimeout()
+
+ expect(timeout).toBe(600000) // Should fall back to default 600 seconds
+ })
+
+ it("should handle NaN by using default", () => {
+ mockGetConfig.mockReturnValue(NaN)
+
+ const timeout = getApiRequestTimeout()
+
+ expect(timeout).toBe(600000) // Should fall back to default 600 seconds
+ })
+
+ it("should handle string values by using default", () => {
+ mockGetConfig.mockReturnValue("not-a-number") // String instead of number
+
+ const timeout = getApiRequestTimeout()
+
+ expect(timeout).toBe(600000) // Should fall back to default since it's not a number
+ })
+
+ it("should handle boolean values by using default", () => {
+ mockGetConfig.mockReturnValue(true) // Boolean instead of number
+
+ const timeout = getApiRequestTimeout()
+
+ expect(timeout).toBe(600000) // Should fall back to default since it's not a number
+ })
+})
diff --git a/src/api/providers/utils/timeout-config.ts b/src/api/providers/utils/timeout-config.ts
new file mode 100644
index 0000000000..94ddf4afc1
--- /dev/null
+++ b/src/api/providers/utils/timeout-config.ts
@@ -0,0 +1,21 @@
+import * as vscode from "vscode"
+
+/**
+ * Gets the API request timeout from VSCode configuration with validation.
+ *
+ * @returns The timeout in milliseconds. Returns 0 for no timeout.
+ */
+export function getApiRequestTimeout(): number {
+ // Get timeout with validation to ensure it's a valid non-negative number
+ const configTimeout = vscode.workspace.getConfiguration("roo-cline").get("apiRequestTimeout", 600)
+
+ // Validate that it's actually a number and not NaN
+ if (typeof configTimeout !== "number" || isNaN(configTimeout)) {
+ return 600 * 1000 // Default to 600 seconds
+ }
+
+ // Allow 0 (no timeout) but clamp negative values to 0
+ const timeoutSeconds = configTimeout < 0 ? 0 : configTimeout
+
+ return timeoutSeconds * 1000 // Convert to milliseconds
+}
diff --git a/src/api/transform/__tests__/reasoning.spec.ts b/src/api/transform/__tests__/reasoning.spec.ts
index 211a02f152..fc0983d741 100644
--- a/src/api/transform/__tests__/reasoning.spec.ts
+++ b/src/api/transform/__tests__/reasoning.spec.ts
@@ -1,6 +1,6 @@
// npx vitest run src/api/transform/__tests__/reasoning.spec.ts
-import type { ModelInfo, ProviderSettings } from "@roo-code/types"
+import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@roo-code/types"
import {
getOpenRouterReasoning,
@@ -154,24 +154,81 @@ describe("reasoning.ts", () => {
const result = getOpenRouterReasoning(optionsWithoutEffort)
- expect(result).toEqual({ effort: undefined })
+ // When reasoningEffort is undefined, the function should return undefined
+ expect(result).toBeUndefined()
})
- it("should handle all reasoning effort values", () => {
- const efforts: Array<"low" | "medium" | "high"> = ["low", "medium", "high"]
+ it("should handle all reasoning effort values including minimal", () => {
+ const efforts: Array = ["minimal", "low", "medium", "high"]
efforts.forEach((effort) => {
const modelWithEffort: ModelInfo = {
...baseModel,
+ supportsReasoningEffort: true,
+ }
+
+ const settingsWithEffort: ProviderSettings = {
reasoningEffort: effort,
}
- const options = { ...baseOptions, model: modelWithEffort, reasoningEffort: effort }
+ const options = {
+ ...baseOptions,
+ model: modelWithEffort,
+ settings: settingsWithEffort,
+ reasoningEffort: effort,
+ }
const result = getOpenRouterReasoning(options)
+ // All effort values including "minimal" should be passed through
expect(result).toEqual({ effort })
})
})
+ it("should handle minimal reasoning effort specifically", () => {
+ const modelWithSupported: ModelInfo = {
+ ...baseModel,
+ supportsReasoningEffort: true,
+ }
+
+ const settingsWithEffort: ProviderSettings = {
+ reasoningEffort: "minimal",
+ }
+
+ const options = {
+ ...baseOptions,
+ model: modelWithSupported,
+ settings: settingsWithEffort,
+ reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
+ }
+
+ const result = getOpenRouterReasoning(options)
+
+ // "minimal" should be passed through to OpenRouter
+ expect(result).toEqual({ effort: "minimal" })
+ })
+
+ it("should handle minimal reasoning effort from settings", () => {
+ const modelWithSupported: ModelInfo = {
+ ...baseModel,
+ supportsReasoningEffort: true,
+ }
+
+ const settingsWithMinimal: ProviderSettings = {
+ reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
+ }
+
+ const options = {
+ ...baseOptions,
+ model: modelWithSupported,
+ settings: settingsWithMinimal,
+ reasoningEffort: "minimal" as ReasoningEffortWithMinimal,
+ }
+
+ const result = getOpenRouterReasoning(options)
+
+ // "minimal" should be passed through to OpenRouter
+ expect(result).toEqual({ effort: "minimal" })
+ })
+
it("should handle zero reasoningBudget", () => {
const modelWithRequired: ModelInfo = {
...baseModel,
diff --git a/src/api/transform/model-params.ts b/src/api/transform/model-params.ts
index cc30aa5605..933697c0a5 100644
--- a/src/api/transform/model-params.ts
+++ b/src/api/transform/model-params.ts
@@ -2,6 +2,7 @@ import {
type ModelInfo,
type ProviderSettings,
type VerbosityLevel,
+ type ReasoningEffortWithMinimal,
ANTHROPIC_DEFAULT_MAX_TOKENS,
} from "@roo-code/types"
@@ -38,7 +39,7 @@ type GetModelParamsOptions = {
type BaseModelParams = {
maxTokens: number | undefined
temperature: number | undefined
- reasoningEffort: "low" | "medium" | "high" | undefined
+ reasoningEffort: ReasoningEffortWithMinimal | undefined
reasoningBudget: number | undefined
verbosity: VerbosityLevel | undefined
}
@@ -128,7 +129,8 @@ export function getModelParams({
temperature = 1.0
} else if (shouldUseReasoningEffort({ model, settings })) {
// "Traditional" reasoning models use the `reasoningEffort` parameter.
- reasoningEffort = customReasoningEffort ?? model.reasoningEffort
+ const effort = customReasoningEffort ?? model.reasoningEffort
+ reasoningEffort = effort as ReasoningEffortWithMinimal
}
const params: BaseModelParams = { maxTokens, temperature, reasoningEffort, reasoningBudget, verbosity }
diff --git a/src/api/transform/reasoning.ts b/src/api/transform/reasoning.ts
index a173c59b19..100b1c2684 100644
--- a/src/api/transform/reasoning.ts
+++ b/src/api/transform/reasoning.ts
@@ -2,14 +2,12 @@ import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta"
import OpenAI from "openai"
import type { GenerateContentConfig } from "@google/genai"
-import type { ModelInfo, ProviderSettings } from "@roo-code/types"
+import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@roo-code/types"
import { shouldUseReasoningBudget, shouldUseReasoningEffort } from "../../shared/api"
-type ReasoningEffort = "low" | "medium" | "high"
-
export type OpenRouterReasoningParams = {
- effort?: ReasoningEffort
+ effort?: ReasoningEffortWithMinimal
max_tokens?: number
exclude?: boolean
}
@@ -23,7 +21,7 @@ export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"]
export type GetModelReasoningOptions = {
model: ModelInfo
reasoningBudget: number | undefined
- reasoningEffort: ReasoningEffort | undefined
+ reasoningEffort: ReasoningEffortWithMinimal | undefined
settings: ProviderSettings
}
@@ -36,7 +34,9 @@ export const getOpenRouterReasoning = ({
shouldUseReasoningBudget({ model, settings })
? { max_tokens: reasoningBudget }
: shouldUseReasoningEffort({ model, settings })
- ? { effort: reasoningEffort }
+ ? reasoningEffort
+ ? { effort: reasoningEffort }
+ : undefined
: undefined
export const getAnthropicReasoning = ({
@@ -50,8 +50,19 @@ export const getOpenAiReasoning = ({
model,
reasoningEffort,
settings,
-}: GetModelReasoningOptions): OpenAiReasoningParams | undefined =>
- shouldUseReasoningEffort({ model, settings }) ? { reasoning_effort: reasoningEffort } : undefined
+}: GetModelReasoningOptions): OpenAiReasoningParams | undefined => {
+ if (!shouldUseReasoningEffort({ model, settings })) {
+ return undefined
+ }
+
+ // If model has reasoning effort capability, return object even if effort is undefined
+ // This preserves the reasoning_effort field in the API call
+ if (reasoningEffort === "minimal") {
+ return undefined
+ }
+
+ return { reasoning_effort: reasoningEffort }
+}
export const getGeminiReasoning = ({
model,
diff --git a/src/core/checkpoints/index.ts b/src/core/checkpoints/index.ts
index 25ae1a2032..83aefe56b5 100644
--- a/src/core/checkpoints/index.ts
+++ b/src/core/checkpoints/index.ts
@@ -23,22 +23,8 @@ export async function getCheckpointService(
if (!cline.enableCheckpoints) {
return undefined
}
-
if (cline.checkpointService) {
- if (cline.checkpointServiceInitializing) {
- console.log("[Task#getCheckpointService] checkpoint service is still initializing")
- const service = cline.checkpointService
- await pWaitFor(
- () => {
- console.log("[Task#getCheckpointService] waiting for service to initialize")
- return service.isInitialized
- },
- { interval, timeout },
- )
- return service.isInitialized ? cline.checkpointService : undefined
- } else {
- return cline.checkpointService
- }
+ return cline.checkpointService
}
const provider = cline.providerRef.deref()
@@ -56,7 +42,7 @@ export async function getCheckpointService(
console.log("[Task#getCheckpointService] initializing checkpoints service")
try {
- const workspaceDir = getWorkspacePath()
+ const workspaceDir = cline.cwd || getWorkspacePath()
if (!workspaceDir) {
log("[Task#getCheckpointService] workspace folder not found, disabling checkpoints")
@@ -78,25 +64,32 @@ export async function getCheckpointService(
shadowDir: globalStorageDir,
log,
}
-
+ if (cline.checkpointServiceInitializing) {
+ await pWaitFor(
+ () => {
+ console.log("[Task#getCheckpointService] waiting for service to initialize")
+ return !!cline.checkpointService && !!cline?.checkpointService?.isInitialized
+ },
+ { interval, timeout },
+ )
+ if (!cline?.checkpointService) {
+ cline.enableCheckpoints = false
+ return undefined
+ }
+ return cline.checkpointService
+ }
+ if (!cline.enableCheckpoints) {
+ return undefined
+ }
const service = RepoPerTaskCheckpointService.create(options)
cline.checkpointServiceInitializing = true
-
- // Check if Git is installed before initializing the service
- // Only assign the service after successful initialization
- try {
- await checkGitInstallation(cline, service, log, provider)
- cline.checkpointService = service
- return service
- } catch (err) {
- // Clean up on failure
- cline.checkpointServiceInitializing = false
- cline.enableCheckpoints = false
- throw err
- }
+ await checkGitInstallation(cline, service, log, provider)
+ cline.checkpointService = service
+ return service
} catch (err) {
log(`[Task#getCheckpointService] ${err.message}`)
cline.enableCheckpoints = false
+ cline.checkpointServiceInitializing = false
return undefined
}
}
@@ -175,13 +168,6 @@ export async function checkpointSave(cline: Task, force = false) {
return
}
- if (!service.isInitialized) {
- const provider = cline.providerRef.deref()
- provider?.log("[checkpointSave] checkpoints didn't initialize in time, disabling checkpoints for this task")
- cline.enableCheckpoints = false
- return
- }
-
TelemetryService.instance.captureCheckpointCreated(cline.taskId)
// Start the checkpoint process in the background.
diff --git a/src/core/prompts/sections/__tests__/custom-instructions.spec.ts b/src/core/prompts/sections/__tests__/custom-instructions.spec.ts
index d015748315..be9c25bcaa 100644
--- a/src/core/prompts/sections/__tests__/custom-instructions.spec.ts
+++ b/src/core/prompts/sections/__tests__/custom-instructions.spec.ts
@@ -775,6 +775,86 @@ describe("addCustomInstructions", () => {
expect(readFileMock).toHaveBeenCalledWith(expect.stringContaining("AGENTS.md"), "utf-8")
})
+ it("should load AGENT.md (singular) when AGENTS.md is not found", async () => {
+ // Simulate no .roo/rules-test-mode directory
+ statMock.mockRejectedValueOnce({ code: "ENOENT" })
+
+ // Mock lstat to indicate AGENTS.md doesn't exist but AGENT.md does
+ lstatMock.mockImplementation((filePath: PathLike) => {
+ const pathStr = filePath.toString()
+ if (pathStr.endsWith("AGENTS.md")) {
+ return Promise.reject({ code: "ENOENT" })
+ }
+ if (pathStr.endsWith("AGENT.md")) {
+ return Promise.resolve({
+ isSymbolicLink: vi.fn().mockReturnValue(false),
+ })
+ }
+ return Promise.reject({ code: "ENOENT" })
+ })
+
+ readFileMock.mockImplementation((filePath: PathLike) => {
+ const pathStr = filePath.toString()
+ if (pathStr.endsWith("AGENT.md")) {
+ return Promise.resolve("Agent rules from AGENT.md file (singular)")
+ }
+ return Promise.reject({ code: "ENOENT" })
+ })
+
+ const result = await addCustomInstructions(
+ "mode instructions",
+ "global instructions",
+ "/fake/path",
+ "test-mode",
+ { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
+ )
+
+ expect(result).toContain("# Agent Rules Standard (AGENT.md):")
+ expect(result).toContain("Agent rules from AGENT.md file (singular)")
+ expect(readFileMock).toHaveBeenCalledWith(expect.stringContaining("AGENT.md"), "utf-8")
+ })
+
+ it("should prefer AGENTS.md over AGENT.md when both exist", async () => {
+ // Simulate no .roo/rules-test-mode directory
+ statMock.mockRejectedValueOnce({ code: "ENOENT" })
+
+ // Mock lstat to indicate both files exist
+ lstatMock.mockImplementation((filePath: PathLike) => {
+ const pathStr = filePath.toString()
+ if (pathStr.endsWith("AGENTS.md") || pathStr.endsWith("AGENT.md")) {
+ return Promise.resolve({
+ isSymbolicLink: vi.fn().mockReturnValue(false),
+ })
+ }
+ return Promise.reject({ code: "ENOENT" })
+ })
+
+ readFileMock.mockImplementation((filePath: PathLike) => {
+ const pathStr = filePath.toString()
+ if (pathStr.endsWith("AGENTS.md")) {
+ return Promise.resolve("Agent rules from AGENTS.md file (plural)")
+ }
+ if (pathStr.endsWith("AGENT.md")) {
+ return Promise.resolve("Agent rules from AGENT.md file (singular)")
+ }
+ return Promise.reject({ code: "ENOENT" })
+ })
+
+ const result = await addCustomInstructions(
+ "mode instructions",
+ "global instructions",
+ "/fake/path",
+ "test-mode",
+ { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
+ )
+
+ // Should contain AGENTS.md content (preferred) and not AGENT.md
+ expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
+ expect(result).toContain("Agent rules from AGENTS.md file (plural)")
+ expect(result).not.toContain("Agent rules from AGENT.md file (singular)")
+ expect(readFileMock).toHaveBeenCalledWith(expect.stringContaining("AGENTS.md"), "utf-8")
+ })
+
it("should return empty string when no instructions provided", async () => {
// Simulate no .roo/rules directory
statMock.mockRejectedValueOnce({ code: "ENOENT" })
diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts
index 22fb6122ec..2d70e45419 100644
--- a/src/core/prompts/sections/custom-instructions.ts
+++ b/src/core/prompts/sections/custom-instructions.ts
@@ -217,40 +217,46 @@ export async function loadRuleFiles(cwd: string): Promise {
}
/**
- * Load AGENTS.md file from the project root if it exists
+ * Load AGENTS.md or AGENT.md file from the project root if it exists
+ * Checks for both AGENTS.md (standard) and AGENT.md (alternative) for compatibility
*/
async function loadAgentRulesFile(cwd: string): Promise {
- try {
- const agentsPath = path.join(cwd, "AGENTS.md")
- let resolvedPath = agentsPath
+ // Try both filenames - AGENTS.md (standard) first, then AGENT.md (alternative)
+ const filenames = ["AGENTS.md", "AGENT.md"]
- // Check if AGENTS.md exists and handle symlinks
+ for (const filename of filenames) {
try {
- const stats = await fs.lstat(agentsPath)
- if (stats.isSymbolicLink()) {
- // Create a temporary fileInfo array to use with resolveSymLink
- const fileInfo: Array<{ originalPath: string; resolvedPath: string }> = []
+ const agentPath = path.join(cwd, filename)
+ let resolvedPath = agentPath
- // Use the existing resolveSymLink function to handle symlink resolution
- await resolveSymLink(agentsPath, fileInfo, 0)
+ // Check if file exists and handle symlinks
+ try {
+ const stats = await fs.lstat(agentPath)
+ if (stats.isSymbolicLink()) {
+ // Create a temporary fileInfo array to use with resolveSymLink
+ const fileInfo: Array<{ originalPath: string; resolvedPath: string }> = []
- // Extract the resolved path from fileInfo
- if (fileInfo.length > 0) {
- resolvedPath = fileInfo[0].resolvedPath
+ // Use the existing resolveSymLink function to handle symlink resolution
+ await resolveSymLink(agentPath, fileInfo, 0)
+
+ // Extract the resolved path from fileInfo
+ if (fileInfo.length > 0) {
+ resolvedPath = fileInfo[0].resolvedPath
+ }
}
+ } catch (err) {
+ // If lstat fails (file doesn't exist), try next filename
+ continue
+ }
+
+ // Read the content from the resolved path
+ const content = await safeReadFile(resolvedPath)
+ if (content) {
+ return `# Agent Rules Standard (${filename}):\n${content}`
}
} catch (err) {
- // If lstat fails (file doesn't exist), return empty
- return ""
+ // Silently ignore errors - agent rules files are optional
}
-
- // Read the content from the resolved path
- const content = await safeReadFile(resolvedPath)
- if (content) {
- return `# Agent Rules Standard (AGENTS.md):\n${content}`
- }
- } catch (err) {
- // Silently ignore errors - AGENTS.md is optional
}
return ""
}
diff --git a/src/core/prompts/tools/codebase-search.ts b/src/core/prompts/tools/codebase-search.ts
index 0fc8f68f8e..baa176a8ed 100644
--- a/src/core/prompts/tools/codebase-search.ts
+++ b/src/core/prompts/tools/codebase-search.ts
@@ -1,19 +1,23 @@
-export function getCodebaseSearchDescription(): string {
+import { ToolArgs } from "./types"
+
+export function getCodebaseSearchDescription(args: ToolArgs): string {
return `## codebase_search
-Description: Find files most relevant to the search query.\nThis is a semantic search tool, so the query should ask for something semantically matching what is needed.\nIf it makes sense to only search in a particular directory, please specify it in the path parameter.\nUnless there is a clear reason to use your own search query, please just reuse the user's exact query with their wording.\nTheir exact wording/phrasing can often be helpful for the semantic search query. Keeping the same exact question format can also be helpful.\nIMPORTANT: Queries MUST be in English. Translate non-English queries before searching.
+Description: Find files most relevant to the search query using semantic search. Searches based on meaning rather than exact text matches. By default searches entire workspace. Reuse the user's exact wording unless there's a clear reason not to - their phrasing often helps semantic search. Queries MUST be in English (translate if needed).
+
Parameters:
-- query: (required) The search query to find relevant code. You should reuse the user's exact query/most recent message with their wording unless there is a clear reason not to.
-- path: (optional) The path to the directory to search in relative to the current working directory. This parameter should only be a directory path, file paths are not supported. Defaults to the current working directory.
+- query: (required) The search query. Reuse the user's exact wording/question format unless there's a clear reason not to.
+- path: (optional) Limit search to specific subdirectory (relative to the current workspace directory ${args.cwd}). Leave empty for entire workspace.
+
Usage:
Your natural language query here
-Path to the directory to search in (optional)
+Optional subdirectory path
-Example: Searching for functions related to user authentication
+Example:
User login and password hashing
-/path/to/directory
+src/auth
`
}
diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts
index 0c88bd94b1..d455abb8d7 100644
--- a/src/core/prompts/tools/index.ts
+++ b/src/core/prompts/tools/index.ts
@@ -39,7 +39,7 @@ const toolDescriptionMap: Record string | undefined>
attempt_completion: (args) => getAttemptCompletionDescription(args),
use_mcp_tool: (args) => getUseMcpToolDescription(args),
access_mcp_resource: (args) => getAccessMcpResourceDescription(args),
- codebase_search: () => getCodebaseSearchDescription(),
+ codebase_search: (args) => getCodebaseSearchDescription(args),
switch_mode: () => getSwitchModeDescription(),
new_task: (args) => getNewTaskDescription(args),
insert_content: (args) => getInsertContentDescription(args),
diff --git a/src/core/protect/RooProtectedController.ts b/src/core/protect/RooProtectedController.ts
index e6d57b22b5..f4d80c4d64 100644
--- a/src/core/protect/RooProtectedController.ts
+++ b/src/core/protect/RooProtectedController.ts
@@ -21,6 +21,7 @@ export class RooProtectedController {
".vscode/**",
".rooprotected", // For future use
"AGENTS.md",
+ "AGENT.md", // Alternative singular form for compatibility
]
constructor(cwd: string) {
diff --git a/src/core/protect/__tests__/RooProtectedController.spec.ts b/src/core/protect/__tests__/RooProtectedController.spec.ts
index 0ee6135e81..f19cf17548 100644
--- a/src/core/protect/__tests__/RooProtectedController.spec.ts
+++ b/src/core/protect/__tests__/RooProtectedController.spec.ts
@@ -48,6 +48,10 @@ describe("RooProtectedController", () => {
expect(controller.isWriteProtected("AGENTS.md")).toBe(true)
})
+ it("should protect AGENT.md file", () => {
+ expect(controller.isWriteProtected("AGENT.md")).toBe(true)
+ })
+
it("should not protect other files starting with .roo", () => {
expect(controller.isWriteProtected(".roosettings")).toBe(false)
expect(controller.isWriteProtected(".rooconfig")).toBe(false)
@@ -147,6 +151,7 @@ describe("RooProtectedController", () => {
".vscode/**",
".rooprotected",
"AGENTS.md",
+ "AGENT.md",
])
})
})
diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts
index 3cb6abe7f7..899ec41401 100644
--- a/src/core/task/Task.ts
+++ b/src/core/task/Task.ts
@@ -11,6 +11,7 @@ import { serializeError } from "serialize-error"
import {
type TaskLike,
+ type TaskMetadata,
type TaskEvents,
type ProviderSettings,
type TokenUsage,
@@ -32,7 +33,7 @@ import {
isBlockingAsk,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
-import { CloudService } from "@roo-code/cloud"
+import { CloudService, ExtensionBridgeService } from "@roo-code/cloud"
// api
import { ApiHandler, ApiHandlerCreateMessageMetadata, buildApiHandler } from "../../api"
@@ -101,12 +102,14 @@ import { restoreTodoListForTask } from "../tools/updateTodoListTool"
import { AutoApprovalHandler } from "./AutoApprovalHandler"
const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes
+const DEFAULT_USAGE_COLLECTION_TIMEOUT_MS = 5000 // 5 seconds
export type TaskOptions = {
provider: ClineProvider
apiConfiguration: ProviderSettings
enableDiff?: boolean
enableCheckpoints?: boolean
+ enableTaskBridge?: boolean
fuzzyMatchThreshold?: number
consecutiveMistakeLimit?: number
task?: string
@@ -121,9 +124,11 @@ export type TaskOptions = {
}
export class Task extends EventEmitter implements TaskLike {
- todoList?: TodoItem[]
readonly taskId: string
readonly instanceId: string
+ readonly metadata: TaskMetadata
+
+ todoList?: TodoItem[]
readonly rootTask: Task | undefined = undefined
readonly parentTask: Task | undefined = undefined
@@ -237,6 +242,10 @@ export class Task extends EventEmitter implements TaskLike {
checkpointService?: RepoPerTaskCheckpointService
checkpointServiceInitializing = false
+ // Task Bridge
+ enableTaskBridge: boolean
+ bridgeService: ExtensionBridgeService | null = null
+
// Streaming
isWaitingForFirstChunk = false
isStreaming = false
@@ -252,12 +261,15 @@ export class Task extends EventEmitter implements TaskLike {
didCompleteReadingStream = false
assistantMessageParser?: AssistantMessageParser
isAssistantMessageParserEnabled = false
+ private lastUsedInstructions?: string
+ private skipPrevResponseIdOnce: boolean = false
constructor({
provider,
apiConfiguration,
enableDiff = false,
enableCheckpoints = true,
+ enableTaskBridge = false,
fuzzyMatchThreshold = 1.0,
consecutiveMistakeLimit = DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
task,
@@ -277,6 +289,12 @@ export class Task extends EventEmitter implements TaskLike {
this.taskId = historyItem ? historyItem.id : crypto.randomUUID()
+ this.metadata = {
+ taskId: this.taskId,
+ task: historyItem ? historyItem.task : task,
+ images: historyItem ? [] : images,
+ }
+
// Normal use-case is usually retry similar history task with new workspace.
this.workspacePath = parentTask
? parentTask.workspacePath
@@ -306,6 +324,7 @@ export class Task extends EventEmitter implements TaskLike {
this.globalStoragePath = provider.context.globalStorageUri.fsPath
this.diffViewProvider = new DiffViewProvider(this.cwd, this)
this.enableCheckpoints = enableCheckpoints
+ this.enableTaskBridge = enableTaskBridge
this.rootTask = rootTask
this.parentTask = parentTask
@@ -738,6 +757,32 @@ export class Task extends EventEmitter implements TaskLike {
this.askResponseImages = images
}
+ public submitUserMessage(text: string, images?: string[]): void {
+ try {
+ const trimmed = (text ?? "").trim()
+ const imgs = images ?? []
+
+ if (!trimmed && imgs.length === 0) {
+ return
+ }
+
+ const provider = this.providerRef.deref()
+ if (!provider) {
+ console.error("[Task#submitUserMessage] Provider reference lost")
+ return
+ }
+
+ void provider.postMessageToWebview({
+ type: "invoke",
+ invoke: "sendMessage",
+ text: trimmed,
+ images: imgs,
+ })
+ } catch (error) {
+ console.error("[Task#submitUserMessage] Failed to submit user message:", error)
+ }
+ }
+
async handleTerminalOperation(terminalOperation: "continue" | "abort") {
if (terminalOperation === "continue") {
this.terminalProcess?.continue()
@@ -824,6 +869,7 @@ export class Task extends EventEmitter implements TaskLike {
progressStatus?: ToolProgressStatus,
options: {
isNonInteractive?: boolean
+ metadata?: Record
} = {},
contextCondense?: ContextCondense,
): Promise {
@@ -861,6 +907,7 @@ export class Task extends EventEmitter implements TaskLike {
images,
partial,
contextCondense,
+ metadata: options.metadata,
})
}
} else {
@@ -876,6 +923,9 @@ export class Task extends EventEmitter implements TaskLike {
lastMessage.images = images
lastMessage.partial = false
lastMessage.progressStatus = progressStatus
+ if (options.metadata) {
+ ;(lastMessage as any).metadata = options.metadata
+ }
// Instead of streaming partialMessage events, we do a save
// and post like normal to persist to disk.
@@ -891,7 +941,15 @@ export class Task extends EventEmitter implements TaskLike {
this.lastMessageTs = sayTs
}
- await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, contextCondense })
+ await this.addToClineMessages({
+ ts: sayTs,
+ type: "say",
+ say: type,
+ text,
+ images,
+ contextCondense,
+ metadata: options.metadata,
+ })
}
}
} else {
@@ -931,6 +989,20 @@ export class Task extends EventEmitter implements TaskLike {
// Start / Abort / Resume
private async startTask(task?: string, images?: string[]): Promise {
+ if (this.enableTaskBridge) {
+ try {
+ this.bridgeService = this.bridgeService || ExtensionBridgeService.getInstance()
+
+ if (this.bridgeService) {
+ await this.bridgeService.subscribeToTask(this)
+ }
+ } catch (error) {
+ console.error(
+ `[Task#startTask] subscribeToTask failed - ${error instanceof Error ? error.message : String(error)}`,
+ )
+ }
+ }
+
// `conversationHistory` (for API) and `clineMessages` (for webview)
// need to be in sync.
// If the extension process were killed, then on restart the
@@ -982,6 +1054,20 @@ export class Task extends EventEmitter implements TaskLike {
}
private async resumeTaskFromHistory() {
+ if (this.enableTaskBridge) {
+ try {
+ this.bridgeService = this.bridgeService || ExtensionBridgeService.getInstance()
+
+ if (this.bridgeService) {
+ await this.bridgeService.subscribeToTask(this)
+ }
+ } catch (error) {
+ console.error(
+ `[Task#resumeTaskFromHistory] subscribeToTask failed - ${error instanceof Error ? error.message : String(error)}`,
+ )
+ }
+ }
+
const modifiedClineMessages = await this.getSavedClineMessages()
// Remove any resume messages that may have been added before
@@ -1221,12 +1307,27 @@ export class Task extends EventEmitter implements TaskLike {
public dispose(): void {
console.log(`[Task] disposing task ${this.taskId}.${this.instanceId}`)
+ // Remove all event listeners to prevent memory leaks
+ try {
+ this.removeAllListeners()
+ } catch (error) {
+ console.error("Error removing event listeners:", error)
+ }
+
// Stop waiting for child task completion.
if (this.pauseInterval) {
clearInterval(this.pauseInterval)
this.pauseInterval = undefined
}
+ // Unsubscribe from TaskBridge service.
+ if (this.bridgeService) {
+ this.bridgeService
+ .unsubscribeFromTask(this.taskId)
+ .catch((error) => console.error("Error unsubscribing from task bridge:", error))
+ this.bridgeService = null
+ }
+
// Release any terminals associated with this task.
try {
// Release any terminals associated with this task.
@@ -1356,463 +1457,639 @@ export class Task extends EventEmitter implements TaskLike {
userContent: Anthropic.Messages.ContentBlockParam[],
includeFileDetails: boolean = false,
): Promise {
- if (this.abort) {
- throw new Error(`[RooCode#recursivelyMakeRooRequests] task ${this.taskId}.${this.instanceId} aborted`)
+ interface StackItem {
+ userContent: Anthropic.Messages.ContentBlockParam[]
+ includeFileDetails: boolean
}
- if (this.consecutiveMistakeLimit > 0 && this.consecutiveMistakeCount >= this.consecutiveMistakeLimit) {
- const { response, text, images } = await this.ask(
- "mistake_limit_reached",
- t("common:errors.mistake_limit_guidance"),
- )
+ const stack: StackItem[] = [{ userContent, includeFileDetails }]
- if (response === "messageResponse") {
- userContent.push(
- ...[
- { type: "text" as const, text: formatResponse.tooManyMistakes(text) },
- ...formatResponse.imageBlocks(images),
- ],
- )
+ while (stack.length > 0) {
+ const currentItem = stack.pop()!
+ const currentUserContent = currentItem.userContent
+ const currentIncludeFileDetails = currentItem.includeFileDetails
- await this.say("user_feedback", text, images)
-
- // Track consecutive mistake errors in telemetry.
- TelemetryService.instance.captureConsecutiveMistakeError(this.taskId)
- }
-
- this.consecutiveMistakeCount = 0
- }
-
- // In this Cline request loop, we need to check if this task instance
- // has been asked to wait for a subtask to finish before continuing.
- const provider = this.providerRef.deref()
-
- if (this.isPaused && provider) {
- provider.log(`[subtasks] paused ${this.taskId}.${this.instanceId}`)
- await this.waitForResume()
- provider.log(`[subtasks] resumed ${this.taskId}.${this.instanceId}`)
- const currentMode = (await provider.getState())?.mode ?? defaultModeSlug
-
- if (currentMode !== this.pausedModeSlug) {
- // The mode has changed, we need to switch back to the paused mode.
- await provider.handleModeSwitch(this.pausedModeSlug)
-
- // Delay to allow mode change to take effect before next tool is executed.
- await delay(500)
-
- provider.log(
- `[subtasks] task ${this.taskId}.${this.instanceId} has switched back to '${this.pausedModeSlug}' from '${currentMode}'`,
- )
- }
- }
-
- // Getting verbose details is an expensive operation, it uses ripgrep to
- // top-down build file structure of project which for large projects can
- // take a few seconds. For the best UX we show a placeholder api_req_started
- // message with a loading spinner as this happens.
-
- // Determine API protocol based on provider and model
- const modelId = getModelId(this.apiConfiguration)
- const apiProtocol = getApiProtocol(this.apiConfiguration.apiProvider, modelId)
-
- await this.say(
- "api_req_started",
- JSON.stringify({
- request:
- userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...",
- apiProtocol,
- }),
- )
-
- const {
- showRooIgnoredFiles = true,
- includeDiagnosticMessages = true,
- maxDiagnosticMessages = 50,
- maxReadFileLine = -1,
- } = (await this.providerRef.deref()?.getState()) ?? {}
-
- const parsedUserContent = await processUserContentMentions({
- userContent,
- cwd: this.cwd,
- urlContentFetcher: this.urlContentFetcher,
- fileContextTracker: this.fileContextTracker,
- rooIgnoreController: this.rooIgnoreController,
- showRooIgnoredFiles,
- includeDiagnosticMessages,
- maxDiagnosticMessages,
- maxReadFileLine,
- })
-
- const environmentDetails = await getEnvironmentDetails(this, includeFileDetails)
-
- // Add environment details as its own text block, separate from tool
- // results.
- const finalUserContent = [...parsedUserContent, { type: "text" as const, text: environmentDetails }]
-
- await this.addToApiConversationHistory({ role: "user", content: finalUserContent })
- TelemetryService.instance.captureConversationMessage(this.taskId, "user")
-
- // Since we sent off a placeholder api_req_started message to update the
- // webview while waiting to actually start the API request (to load
- // potential details for example), we need to update the text of that
- // message.
- const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
-
- this.clineMessages[lastApiReqIndex].text = JSON.stringify({
- request: finalUserContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"),
- apiProtocol,
- } satisfies ClineApiReqInfo)
-
- await this.saveClineMessages()
- await provider?.postStateToWebview()
-
- try {
- let cacheWriteTokens = 0
- let cacheReadTokens = 0
- let inputTokens = 0
- let outputTokens = 0
- let totalCost: number | undefined
-
- // We can't use `api_req_finished` anymore since it's a unique case
- // where it could come after a streaming message (i.e. in the middle
- // of being updated or executed).
- // Fortunately `api_req_finished` was always parsed out for the GUI
- // anyways, so it remains solely for legacy purposes to keep track
- // of prices in tasks from history (it's worth removing a few months
- // from now).
- const updateApiReqMsg = (cancelReason?: ClineApiReqCancelReason, streamingFailedMessage?: string) => {
- const existingData = JSON.parse(this.clineMessages[lastApiReqIndex].text || "{}")
- this.clineMessages[lastApiReqIndex].text = JSON.stringify({
- ...existingData,
- tokensIn: inputTokens,
- tokensOut: outputTokens,
- cacheWrites: cacheWriteTokens,
- cacheReads: cacheReadTokens,
- cost:
- totalCost ??
- calculateApiCostAnthropic(
- this.api.getModel().info,
- inputTokens,
- outputTokens,
- cacheWriteTokens,
- cacheReadTokens,
- ),
- cancelReason,
- streamingFailedMessage,
- } satisfies ClineApiReqInfo)
- }
-
- const abortStream = async (cancelReason: ClineApiReqCancelReason, streamingFailedMessage?: string) => {
- if (this.diffViewProvider.isEditing) {
- await this.diffViewProvider.revertChanges() // closes diff view
- }
-
- // if last message is a partial we need to update and save it
- const lastMessage = this.clineMessages.at(-1)
-
- if (lastMessage && 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
- console.log("updating partial message", lastMessage)
- // await this.saveClineMessages()
- }
-
- // Let assistant know their response was interrupted for when task is resumed
- await this.addToApiConversationHistory({
- role: "assistant",
- content: [
- {
- type: "text",
- text:
- assistantMessage +
- `\n\n[${
- cancelReason === "streaming_failed"
- ? "Response interrupted by API Error"
- : "Response interrupted by user"
- }]`,
- },
- ],
- })
-
- // Update `api_req_started` to have cancelled and cost, so that
- // we can display the cost of the partial stream.
- updateApiReqMsg(cancelReason, streamingFailedMessage)
- await this.saveClineMessages()
-
- // Signals to provider that it can retrieve the saved messages
- // from disk, as abortTask can not be awaited on in nature.
- this.didFinishAbortingStream = true
- }
-
- // Reset streaming state.
- this.currentStreamingContentIndex = 0
- this.currentStreamingDidCheckpoint = false
- this.assistantMessageContent = []
- this.didCompleteReadingStream = false
- this.userMessageContent = []
- this.userMessageContentReady = false
- this.didRejectTool = false
- this.didAlreadyUseTool = false
- this.presentAssistantMessageLocked = false
- this.presentAssistantMessageHasPendingUpdates = false
- if (this.assistantMessageParser) {
- this.assistantMessageParser.reset()
- }
-
- await this.diffViewProvider.reset()
-
- // Yields only if the first chunk is successful, otherwise will
- // allow the user to retry the request (most likely due to rate
- // limit error, which gets thrown on the first chunk).
- const stream = this.attemptApiRequest()
- let assistantMessage = ""
- let reasoningMessage = ""
- this.isStreaming = true
-
- try {
- for await (const chunk of stream) {
- if (!chunk) {
- // Sometimes chunk is undefined, no idea that can cause
- // it, but this workaround seems to fix it.
- continue
- }
-
- switch (chunk.type) {
- case "reasoning":
- reasoningMessage += chunk.text
- await this.say("reasoning", reasoningMessage, undefined, true)
- break
- case "usage":
- inputTokens += chunk.inputTokens
- outputTokens += chunk.outputTokens
- cacheWriteTokens += chunk.cacheWriteTokens ?? 0
- cacheReadTokens += chunk.cacheReadTokens ?? 0
- totalCost = chunk.totalCost
- break
- case "text": {
- assistantMessage += chunk.text
-
- // Parse raw assistant message chunk into content blocks.
- const prevLength = this.assistantMessageContent.length
- if (this.isAssistantMessageParserEnabled && this.assistantMessageParser) {
- this.assistantMessageContent = this.assistantMessageParser.processChunk(chunk.text)
- } else {
- // Use the old parsing method when experiment is disabled
- this.assistantMessageContent = parseAssistantMessage(assistantMessage)
- }
-
- if (this.assistantMessageContent.length > prevLength) {
- // New content we need to present, reset to
- // false in case previous content set this to true.
- this.userMessageContentReady = false
- }
-
- // Present content to user.
- presentAssistantMessage(this)
- break
- }
- }
-
- if (this.abort) {
- console.log(`aborting stream, this.abandoned = ${this.abandoned}`)
-
- if (!this.abandoned) {
- // Only need to gracefully abort if this instance
- // isn't abandoned (sometimes OpenRouter stream
- // hangs, in which case this would affect future
- // instances of Cline).
- await abortStream("user_cancelled")
- }
-
- break // Aborts the stream.
- }
-
- if (this.didRejectTool) {
- // `userContent` has a tool rejection, so interrupt the
- // assistant's response to present the user's feedback.
- assistantMessage += "\n\n[Response interrupted by user feedback]"
- // Instead of setting this preemptively, we allow the
- // present iterator to finish and set
- // userMessageContentReady when its ready.
- // this.userMessageContentReady = true
- break
- }
-
- // PREV: We need to let the request finish for openrouter to
- // get generation details.
- // UPDATE: It's better UX to interrupt the request at the
- // cost of the API cost not being retrieved.
- if (this.didAlreadyUseTool) {
- assistantMessage +=
- "\n\n[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]"
- break
- }
- }
- } catch (error) {
- // Abandoned happens when extension is no longer waiting for the
- // Cline instance to finish aborting (error is thrown here when
- // any function in the for loop throws due to this.abort).
- if (!this.abandoned) {
- // If the stream failed, there's various states the task
- // could be in (i.e. could have streamed some tools the user
- // may have executed), so we just resort to replicating a
- // cancel task.
-
- // Check if this was a user-initiated cancellation BEFORE calling abortTask
- // If this.abort is already true, it means the user clicked cancel, so we should
- // treat this as "user_cancelled" rather than "streaming_failed"
- const cancelReason = this.abort ? "user_cancelled" : "streaming_failed"
-
- const streamingFailedMessage = this.abort
- ? undefined
- : (error.message ?? JSON.stringify(serializeError(error), null, 2))
-
- // Now call abortTask after determining the cancel reason.
- await this.abortTask()
- await abortStream(cancelReason, streamingFailedMessage)
-
- const history = await provider?.getTaskWithId(this.taskId)
-
- if (history) {
- await provider?.initClineWithHistoryItem(history.historyItem)
- }
- }
- } finally {
- this.isStreaming = false
- }
-
- if (inputTokens > 0 || outputTokens > 0 || cacheWriteTokens > 0 || cacheReadTokens > 0) {
- TelemetryService.instance.captureLlmCompletion(this.taskId, {
- inputTokens,
- outputTokens,
- cacheWriteTokens,
- cacheReadTokens,
- cost:
- totalCost ??
- calculateApiCostAnthropic(
- this.api.getModel().info,
- inputTokens,
- outputTokens,
- cacheWriteTokens,
- cacheReadTokens,
- ),
- })
- }
-
- // Need to call here in case the stream was aborted.
- if (this.abort || this.abandoned) {
+ if (this.abort) {
throw new Error(`[RooCode#recursivelyMakeRooRequests] task ${this.taskId}.${this.instanceId} aborted`)
}
- this.didCompleteReadingStream = true
-
- // Set any blocks to be complete to allow `presentAssistantMessage`
- // to finish and set `userMessageContentReady` to true.
- // (Could be a text block that had no subsequent tool uses, or a
- // text block at the very end, or an invalid tool use, etc. Whatever
- // the case, `presentAssistantMessage` relies on these blocks either
- // to be completed or the user to reject a block in order to proceed
- // and eventually set userMessageContentReady to true.)
- const partialBlocks = this.assistantMessageContent.filter((block) => block.partial)
- partialBlocks.forEach((block) => (block.partial = false))
-
- // Can't just do this b/c a tool could be in the middle of executing.
- // this.assistantMessageContent.forEach((e) => (e.partial = false))
-
- // Now that the stream is complete, finalize any remaining partial content blocks
- if (this.isAssistantMessageParserEnabled && this.assistantMessageParser) {
- this.assistantMessageParser.finalizeContentBlocks()
- this.assistantMessageContent = this.assistantMessageParser.getContentBlocks()
- }
- // When using old parser, no finalization needed - parsing already happened during streaming
-
- if (partialBlocks.length > 0) {
- // If there is content to update then it will complete and
- // update `this.userMessageContentReady` to true, which we
- // `pWaitFor` before making the next request. All this is really
- // doing is presenting the last partial message that we just set
- // to complete.
- presentAssistantMessage(this)
- }
-
- updateApiReqMsg()
- await this.saveClineMessages()
- await this.providerRef.deref()?.postStateToWebview()
-
- // Reset parser after each complete conversation round
- if (this.assistantMessageParser) {
- this.assistantMessageParser.reset()
- }
-
- // Now add to apiConversationHistory.
- // Need to save assistant responses to file before proceeding to
- // tool use since user can exit at any moment and we wouldn't be
- // able to save the assistant's response.
- let didEndLoop = false
-
- if (assistantMessage.length > 0) {
- await this.addToApiConversationHistory({
- role: "assistant",
- content: [{ type: "text", text: assistantMessage }],
- })
-
- TelemetryService.instance.captureConversationMessage(this.taskId, "assistant")
-
- // NOTE: This comment is here for future reference - this was a
- // workaround for `userMessageContent` not getting set to true.
- // It was due to it not recursively calling for partial blocks
- // when `didRejectTool`, so it would get stuck waiting for a
- // partial block to complete before it could continue.
- // In case the content blocks finished it may be the api stream
- // finished after the last parsed content block was executed, so
- // we are able to detect out of bounds and set
- // `userMessageContentReady` to true (note you should not call
- // `presentAssistantMessage` since if the last block i
- // completed it will be presented again).
- // const completeBlocks = this.assistantMessageContent.filter((block) => !block.partial) // If there are any partial blocks after the stream ended we can consider them invalid.
- // if (this.currentStreamingContentIndex >= completeBlocks.length) {
- // this.userMessageContentReady = true
- // }
-
- await pWaitFor(() => this.userMessageContentReady)
-
- // If the model did not tool use, then we need to tell it to
- // either use a tool or attempt_completion.
- const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use")
-
- if (!didToolUse) {
- this.userMessageContent.push({ type: "text", text: formatResponse.noToolsUsed() })
- this.consecutiveMistakeCount++
- }
-
- const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent)
- didEndLoop = recDidEndLoop
- } else {
- // If there's no assistant_responses, that means we got no text
- // or tool_use content blocks from API which we should assume is
- // an error.
- await this.say(
- "error",
- "Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output.",
+ if (this.consecutiveMistakeLimit > 0 && this.consecutiveMistakeCount >= this.consecutiveMistakeLimit) {
+ const { response, text, images } = await this.ask(
+ "mistake_limit_reached",
+ t("common:errors.mistake_limit_guidance"),
)
- await this.addToApiConversationHistory({
- role: "assistant",
- content: [{ type: "text", text: "Failure: I did not provide a response." }],
- })
+ if (response === "messageResponse") {
+ currentUserContent.push(
+ ...[
+ { type: "text" as const, text: formatResponse.tooManyMistakes(text) },
+ ...formatResponse.imageBlocks(images),
+ ],
+ )
+
+ await this.say("user_feedback", text, images)
+
+ // Track consecutive mistake errors in telemetry.
+ TelemetryService.instance.captureConsecutiveMistakeError(this.taskId)
+ }
+
+ this.consecutiveMistakeCount = 0
}
- return didEndLoop // Will always be false for now.
- } catch (error) {
- // This should never happen since the only thing that can throw an
- // error is the attemptApiRequest, which is wrapped in a try catch
- // that sends an ask where if noButtonClicked, will clear current
- // task and destroy this instance. However to avoid unhandled
- // promise rejection, we will end this loop which will end execution
- // of this instance (see `startTask`).
- return true // Needs to be true so parent loop knows to end task.
+ // In this Cline request loop, we need to check if this task instance
+ // has been asked to wait for a subtask to finish before continuing.
+ const provider = this.providerRef.deref()
+
+ if (this.isPaused && provider) {
+ provider.log(`[subtasks] paused ${this.taskId}.${this.instanceId}`)
+ await this.waitForResume()
+ provider.log(`[subtasks] resumed ${this.taskId}.${this.instanceId}`)
+ const currentMode = (await provider.getState())?.mode ?? defaultModeSlug
+
+ if (currentMode !== this.pausedModeSlug) {
+ // The mode has changed, we need to switch back to the paused mode.
+ await provider.handleModeSwitch(this.pausedModeSlug)
+
+ // Delay to allow mode change to take effect before next tool is executed.
+ await delay(500)
+
+ provider.log(
+ `[subtasks] task ${this.taskId}.${this.instanceId} has switched back to '${this.pausedModeSlug}' from '${currentMode}'`,
+ )
+ }
+ }
+
+ // Getting verbose details is an expensive operation, it uses ripgrep to
+ // top-down build file structure of project which for large projects can
+ // take a few seconds. For the best UX we show a placeholder api_req_started
+ // message with a loading spinner as this happens.
+
+ // Determine API protocol based on provider and model
+ const modelId = getModelId(this.apiConfiguration)
+ const apiProtocol = getApiProtocol(this.apiConfiguration.apiProvider, modelId)
+
+ await this.say(
+ "api_req_started",
+ JSON.stringify({
+ request:
+ currentUserContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") +
+ "\n\nLoading...",
+ apiProtocol,
+ }),
+ )
+
+ const {
+ showRooIgnoredFiles = true,
+ includeDiagnosticMessages = true,
+ maxDiagnosticMessages = 50,
+ maxReadFileLine = -1,
+ } = (await this.providerRef.deref()?.getState()) ?? {}
+
+ const parsedUserContent = await processUserContentMentions({
+ userContent: currentUserContent,
+ cwd: this.cwd,
+ urlContentFetcher: this.urlContentFetcher,
+ fileContextTracker: this.fileContextTracker,
+ rooIgnoreController: this.rooIgnoreController,
+ showRooIgnoredFiles,
+ includeDiagnosticMessages,
+ maxDiagnosticMessages,
+ maxReadFileLine,
+ })
+
+ const environmentDetails = await getEnvironmentDetails(this, currentIncludeFileDetails)
+
+ // Add environment details as its own text block, separate from tool
+ // results.
+ const finalUserContent = [...parsedUserContent, { type: "text" as const, text: environmentDetails }]
+
+ await this.addToApiConversationHistory({ role: "user", content: finalUserContent })
+ TelemetryService.instance.captureConversationMessage(this.taskId, "user")
+
+ // Since we sent off a placeholder api_req_started message to update the
+ // webview while waiting to actually start the API request (to load
+ // potential details for example), we need to update the text of that
+ // message.
+ const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
+
+ this.clineMessages[lastApiReqIndex].text = JSON.stringify({
+ request: finalUserContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"),
+ apiProtocol,
+ } satisfies ClineApiReqInfo)
+
+ await this.saveClineMessages()
+ await provider?.postStateToWebview()
+
+ try {
+ let cacheWriteTokens = 0
+ let cacheReadTokens = 0
+ let inputTokens = 0
+ let outputTokens = 0
+ let totalCost: number | undefined
+
+ // We can't use `api_req_finished` anymore since it's a unique case
+ // where it could come after a streaming message (i.e. in the middle
+ // of being updated or executed).
+ // Fortunately `api_req_finished` was always parsed out for the GUI
+ // anyways, so it remains solely for legacy purposes to keep track
+ // of prices in tasks from history (it's worth removing a few months
+ // from now).
+ const updateApiReqMsg = (cancelReason?: ClineApiReqCancelReason, streamingFailedMessage?: string) => {
+ if (lastApiReqIndex < 0 || !this.clineMessages[lastApiReqIndex]) {
+ return
+ }
+
+ const existingData = JSON.parse(this.clineMessages[lastApiReqIndex].text || "{}")
+ this.clineMessages[lastApiReqIndex].text = JSON.stringify({
+ ...existingData,
+ tokensIn: inputTokens,
+ tokensOut: outputTokens,
+ cacheWrites: cacheWriteTokens,
+ cacheReads: cacheReadTokens,
+ cost:
+ totalCost ??
+ calculateApiCostAnthropic(
+ this.api.getModel().info,
+ inputTokens,
+ outputTokens,
+ cacheWriteTokens,
+ cacheReadTokens,
+ ),
+ cancelReason,
+ streamingFailedMessage,
+ } satisfies ClineApiReqInfo)
+ }
+
+ const abortStream = async (cancelReason: ClineApiReqCancelReason, streamingFailedMessage?: string) => {
+ if (this.diffViewProvider.isEditing) {
+ await this.diffViewProvider.revertChanges() // closes diff view
+ }
+
+ // if last message is a partial we need to update and save it
+ const lastMessage = this.clineMessages.at(-1)
+
+ if (lastMessage && 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
+ console.log("updating partial message", lastMessage)
+ // await this.saveClineMessages()
+ }
+
+ // Let assistant know their response was interrupted for when task is resumed
+ await this.addToApiConversationHistory({
+ role: "assistant",
+ content: [
+ {
+ type: "text",
+ text:
+ assistantMessage +
+ `\n\n[${
+ cancelReason === "streaming_failed"
+ ? "Response interrupted by API Error"
+ : "Response interrupted by user"
+ }]`,
+ },
+ ],
+ })
+
+ // Update `api_req_started` to have cancelled and cost, so that
+ // we can display the cost of the partial stream.
+ updateApiReqMsg(cancelReason, streamingFailedMessage)
+ await this.saveClineMessages()
+
+ // Signals to provider that it can retrieve the saved messages
+ // from disk, as abortTask can not be awaited on in nature.
+ this.didFinishAbortingStream = true
+ }
+
+ // Reset streaming state.
+ this.currentStreamingContentIndex = 0
+ this.currentStreamingDidCheckpoint = false
+ this.assistantMessageContent = []
+ this.didCompleteReadingStream = false
+ this.userMessageContent = []
+ this.userMessageContentReady = false
+ this.didRejectTool = false
+ this.didAlreadyUseTool = false
+ this.presentAssistantMessageLocked = false
+ this.presentAssistantMessageHasPendingUpdates = false
+ if (this.assistantMessageParser) {
+ this.assistantMessageParser.reset()
+ }
+
+ await this.diffViewProvider.reset()
+
+ // Yields only if the first chunk is successful, otherwise will
+ // allow the user to retry the request (most likely due to rate
+ // limit error, which gets thrown on the first chunk).
+ const stream = this.attemptApiRequest()
+ let assistantMessage = ""
+ let reasoningMessage = ""
+ this.isStreaming = true
+
+ try {
+ const iterator = stream[Symbol.asyncIterator]()
+ let item = await iterator.next()
+ while (!item.done) {
+ const chunk = item.value
+ item = await iterator.next()
+ if (!chunk) {
+ // Sometimes chunk is undefined, no idea that can cause
+ // it, but this workaround seems to fix it.
+ continue
+ }
+
+ switch (chunk.type) {
+ case "reasoning":
+ reasoningMessage += chunk.text
+ await this.say("reasoning", reasoningMessage, undefined, true)
+ break
+ case "usage":
+ inputTokens += chunk.inputTokens
+ outputTokens += chunk.outputTokens
+ cacheWriteTokens += chunk.cacheWriteTokens ?? 0
+ cacheReadTokens += chunk.cacheReadTokens ?? 0
+ totalCost = chunk.totalCost
+ break
+ case "text": {
+ assistantMessage += chunk.text
+
+ // Parse raw assistant message chunk into content blocks.
+ const prevLength = this.assistantMessageContent.length
+ if (this.isAssistantMessageParserEnabled && this.assistantMessageParser) {
+ this.assistantMessageContent = this.assistantMessageParser.processChunk(chunk.text)
+ } else {
+ // Use the old parsing method when experiment is disabled
+ this.assistantMessageContent = parseAssistantMessage(assistantMessage)
+ }
+
+ if (this.assistantMessageContent.length > prevLength) {
+ // New content we need to present, reset to
+ // false in case previous content set this to true.
+ this.userMessageContentReady = false
+ }
+
+ // Present content to user.
+ presentAssistantMessage(this)
+ break
+ }
+ }
+
+ if (this.abort) {
+ console.log(`aborting stream, this.abandoned = ${this.abandoned}`)
+
+ if (!this.abandoned) {
+ // Only need to gracefully abort if this instance
+ // isn't abandoned (sometimes OpenRouter stream
+ // hangs, in which case this would affect future
+ // instances of Cline).
+ await abortStream("user_cancelled")
+ }
+
+ break // Aborts the stream.
+ }
+
+ if (this.didRejectTool) {
+ // `userContent` has a tool rejection, so interrupt the
+ // assistant's response to present the user's feedback.
+ assistantMessage += "\n\n[Response interrupted by user feedback]"
+ // Instead of setting this preemptively, we allow the
+ // present iterator to finish and set
+ // userMessageContentReady when its ready.
+ // this.userMessageContentReady = true
+ break
+ }
+
+ if (this.didAlreadyUseTool) {
+ assistantMessage +=
+ "\n\n[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]"
+ break
+ }
+ }
+
+ // Create a copy of current token values to avoid race conditions
+ const currentTokens = {
+ input: inputTokens,
+ output: outputTokens,
+ cacheWrite: cacheWriteTokens,
+ cacheRead: cacheReadTokens,
+ total: totalCost,
+ }
+
+ const drainStreamInBackgroundToFindAllUsage = async (apiReqIndex: number) => {
+ const timeoutMs = DEFAULT_USAGE_COLLECTION_TIMEOUT_MS
+ const startTime = Date.now()
+ const modelId = getModelId(this.apiConfiguration)
+
+ // Local variables to accumulate usage data without affecting the main flow
+ let bgInputTokens = currentTokens.input
+ let bgOutputTokens = currentTokens.output
+ let bgCacheWriteTokens = currentTokens.cacheWrite
+ let bgCacheReadTokens = currentTokens.cacheRead
+ let bgTotalCost = currentTokens.total
+
+ // Helper function to capture telemetry and update messages
+ const captureUsageData = async (
+ tokens: {
+ input: number
+ output: number
+ cacheWrite: number
+ cacheRead: number
+ total?: number
+ },
+ messageIndex: number = apiReqIndex,
+ ) => {
+ if (
+ tokens.input > 0 ||
+ tokens.output > 0 ||
+ tokens.cacheWrite > 0 ||
+ tokens.cacheRead > 0
+ ) {
+ // Update the shared variables atomically
+ inputTokens = tokens.input
+ outputTokens = tokens.output
+ cacheWriteTokens = tokens.cacheWrite
+ cacheReadTokens = tokens.cacheRead
+ totalCost = tokens.total
+
+ // Update the API request message with the latest usage data
+ updateApiReqMsg()
+ await this.saveClineMessages()
+
+ // Update the specific message in the webview
+ const apiReqMessage = this.clineMessages[messageIndex]
+ if (apiReqMessage) {
+ await this.updateClineMessage(apiReqMessage)
+ }
+
+ // Capture telemetry
+ TelemetryService.instance.captureLlmCompletion(this.taskId, {
+ inputTokens: tokens.input,
+ outputTokens: tokens.output,
+ cacheWriteTokens: tokens.cacheWrite,
+ cacheReadTokens: tokens.cacheRead,
+ cost:
+ tokens.total ??
+ calculateApiCostAnthropic(
+ this.api.getModel().info,
+ tokens.input,
+ tokens.output,
+ tokens.cacheWrite,
+ tokens.cacheRead,
+ ),
+ })
+ }
+ }
+
+ try {
+ // Continue processing the original stream from where the main loop left off
+ let usageFound = false
+ let chunkCount = 0
+
+ // Use the same iterator that the main loop was using
+ while (!item.done) {
+ // Check for timeout
+ if (Date.now() - startTime > timeoutMs) {
+ console.warn(
+ `[Background Usage Collection] Timed out after ${timeoutMs}ms for model: ${modelId}, processed ${chunkCount} chunks`,
+ )
+ // Clean up the iterator before breaking
+ if (iterator.return) {
+ await iterator.return(undefined)
+ }
+ break
+ }
+
+ const chunk = item.value
+ item = await iterator.next()
+ chunkCount++
+
+ if (chunk && chunk.type === "usage") {
+ usageFound = true
+ bgInputTokens += chunk.inputTokens
+ bgOutputTokens += chunk.outputTokens
+ bgCacheWriteTokens += chunk.cacheWriteTokens ?? 0
+ bgCacheReadTokens += chunk.cacheReadTokens ?? 0
+ bgTotalCost = chunk.totalCost
+ }
+ }
+
+ if (
+ usageFound ||
+ bgInputTokens > 0 ||
+ bgOutputTokens > 0 ||
+ bgCacheWriteTokens > 0 ||
+ bgCacheReadTokens > 0
+ ) {
+ // We have usage data either from a usage chunk or accumulated tokens
+ await captureUsageData(
+ {
+ input: bgInputTokens,
+ output: bgOutputTokens,
+ cacheWrite: bgCacheWriteTokens,
+ cacheRead: bgCacheReadTokens,
+ total: bgTotalCost,
+ },
+ lastApiReqIndex,
+ )
+ } else {
+ console.warn(
+ `[Background Usage Collection] Suspicious: request ${apiReqIndex} is complete, but no usage info was found. Model: ${modelId}`,
+ )
+ }
+ } catch (error) {
+ console.error("Error draining stream for usage data:", error)
+ // Still try to capture whatever usage data we have collected so far
+ if (
+ bgInputTokens > 0 ||
+ bgOutputTokens > 0 ||
+ bgCacheWriteTokens > 0 ||
+ bgCacheReadTokens > 0
+ ) {
+ await captureUsageData(
+ {
+ input: bgInputTokens,
+ output: bgOutputTokens,
+ cacheWrite: bgCacheWriteTokens,
+ cacheRead: bgCacheReadTokens,
+ total: bgTotalCost,
+ },
+ lastApiReqIndex,
+ )
+ }
+ }
+ }
+
+ // Start the background task and handle any errors
+ drainStreamInBackgroundToFindAllUsage(lastApiReqIndex).catch((error) => {
+ console.error("Background usage collection failed:", error)
+ })
+ } catch (error) {
+ // Abandoned happens when extension is no longer waiting for the
+ // Cline instance to finish aborting (error is thrown here when
+ // any function in the for loop throws due to this.abort).
+ if (!this.abandoned) {
+ // If the stream failed, there's various states the task
+ // could be in (i.e. could have streamed some tools the user
+ // may have executed), so we just resort to replicating a
+ // cancel task.
+
+ // Check if this was a user-initiated cancellation BEFORE calling abortTask
+ // If this.abort is already true, it means the user clicked cancel, so we should
+ // treat this as "user_cancelled" rather than "streaming_failed"
+ const cancelReason = this.abort ? "user_cancelled" : "streaming_failed"
+
+ const streamingFailedMessage = this.abort
+ ? undefined
+ : (error.message ?? JSON.stringify(serializeError(error), null, 2))
+
+ // Now call abortTask after determining the cancel reason.
+ await this.abortTask()
+ await abortStream(cancelReason, streamingFailedMessage)
+
+ const history = await provider?.getTaskWithId(this.taskId)
+
+ if (history) {
+ await provider?.initClineWithHistoryItem(history.historyItem)
+ }
+ }
+ } finally {
+ this.isStreaming = false
+ }
+
+ // Need to call here in case the stream was aborted.
+ if (this.abort || this.abandoned) {
+ throw new Error(
+ `[RooCode#recursivelyMakeRooRequests] task ${this.taskId}.${this.instanceId} aborted`,
+ )
+ }
+
+ this.didCompleteReadingStream = true
+
+ // Set any blocks to be complete to allow `presentAssistantMessage`
+ // to finish and set `userMessageContentReady` to true.
+ // (Could be a text block that had no subsequent tool uses, or a
+ // text block at the very end, or an invalid tool use, etc. Whatever
+ // the case, `presentAssistantMessage` relies on these blocks either
+ // to be completed or the user to reject a block in order to proceed
+ // and eventually set userMessageContentReady to true.)
+ const partialBlocks = this.assistantMessageContent.filter((block) => block.partial)
+ partialBlocks.forEach((block) => (block.partial = false))
+
+ // Can't just do this b/c a tool could be in the middle of executing.
+ // this.assistantMessageContent.forEach((e) => (e.partial = false))
+
+ // Now that the stream is complete, finalize any remaining partial content blocks
+ if (this.isAssistantMessageParserEnabled && this.assistantMessageParser) {
+ this.assistantMessageParser.finalizeContentBlocks()
+ this.assistantMessageContent = this.assistantMessageParser.getContentBlocks()
+ }
+ // When using old parser, no finalization needed - parsing already happened during streaming
+
+ if (partialBlocks.length > 0) {
+ // If there is content to update then it will complete and
+ // update `this.userMessageContentReady` to true, which we
+ // `pWaitFor` before making the next request. All this is really
+ // doing is presenting the last partial message that we just set
+ // to complete.
+ presentAssistantMessage(this)
+ }
+
+ // Note: updateApiReqMsg() is now called from within drainStreamInBackgroundToFindAllUsage
+ // to ensure usage data is captured even when the stream is interrupted. The background task
+ // uses local variables to accumulate usage data before atomically updating the shared state.
+ await this.persistGpt5Metadata(reasoningMessage)
+ await this.saveClineMessages()
+ await this.providerRef.deref()?.postStateToWebview()
+
+ // Reset parser after each complete conversation round
+ if (this.assistantMessageParser) {
+ this.assistantMessageParser.reset()
+ }
+
+ // Now add to apiConversationHistory.
+ // Need to save assistant responses to file before proceeding to
+ // tool use since user can exit at any moment and we wouldn't be
+ // able to save the assistant's response.
+ let didEndLoop = false
+
+ if (assistantMessage.length > 0) {
+ await this.addToApiConversationHistory({
+ role: "assistant",
+ content: [{ type: "text", text: assistantMessage }],
+ })
+
+ TelemetryService.instance.captureConversationMessage(this.taskId, "assistant")
+
+ // NOTE: This comment is here for future reference - this was a
+ // workaround for `userMessageContent` not getting set to true.
+ // It was due to it not recursively calling for partial blocks
+ // when `didRejectTool`, so it would get stuck waiting for a
+ // partial block to complete before it could continue.
+ // In case the content blocks finished it may be the api stream
+ // finished after the last parsed content block was executed, so
+ // we are able to detect out of bounds and set
+ // `userMessageContentReady` to true (note you should not call
+ // `presentAssistantMessage` since if the last block i
+ // completed it will be presented again).
+ // const completeBlocks = this.assistantMessageContent.filter((block) => !block.partial) // If there are any partial blocks after the stream ended we can consider them invalid.
+ // if (this.currentStreamingContentIndex >= completeBlocks.length) {
+ // this.userMessageContentReady = true
+ // }
+
+ await pWaitFor(() => this.userMessageContentReady)
+
+ // If the model did not tool use, then we need to tell it to
+ // either use a tool or attempt_completion.
+ const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use")
+
+ if (!didToolUse) {
+ this.userMessageContent.push({ type: "text", text: formatResponse.noToolsUsed() })
+ this.consecutiveMistakeCount++
+ }
+
+ if (this.userMessageContent.length > 0) {
+ stack.push({
+ userContent: [...this.userMessageContent], // Create a copy to avoid mutation issues
+ includeFileDetails: false, // Subsequent iterations don't need file details
+ })
+
+ // Add periodic yielding to prevent blocking
+ await new Promise((resolve) => setImmediate(resolve))
+ }
+ // Continue to next iteration instead of setting didEndLoop from recursive call
+ continue
+ } else {
+ // If there's no assistant_responses, that means we got no text
+ // or tool_use content blocks from API which we should assume is
+ // an error.
+ await this.say(
+ "error",
+ "Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output.",
+ )
+
+ await this.addToApiConversationHistory({
+ role: "assistant",
+ content: [{ type: "text", text: "Failure: I did not provide a response." }],
+ })
+ }
+
+ // If we reach here without continuing, return false (will always be false for now)
+ return false
+ } catch (error) {
+ // This should never happen since the only thing that can throw an
+ // error is the attemptApiRequest, which is wrapped in a try catch
+ // that sends an ask where if noButtonClicked, will clear current
+ // task and destroy this instance. However to avoid unhandled
+ // promise rejection, we will end this loop which will end execution
+ // of this instance (see `startTask`).
+ return true // Needs to be true so parent loop knows to end task.
+ }
}
+
+ // If we exit the while loop normally (stack is empty), return false
+ return false
}
private async getSystemPrompt(): Promise {
@@ -1954,6 +2231,7 @@ export class Task extends EventEmitter implements TaskLike {
Task.lastGlobalApiRequestTime = Date.now()
const systemPrompt = await this.getSystemPrompt()
+ this.lastUsedInstructions = systemPrompt
const { contextTokens } = this.getTokenUsage()
if (contextTokens) {
@@ -1992,6 +2270,10 @@ export class Task extends EventEmitter implements TaskLike {
if (truncateResult.error) {
await this.say("condense_context_error", truncateResult.error)
} else if (truncateResult.summary) {
+ // A condense operation occurred; for the next GPT‑5 API call we should NOT
+ // send previous_response_id so the request reflects the fresh condensed context.
+ this.skipPrevResponseIdOnce = true
+
const { summary, cost, prevContextTokens, newContextTokens = 0 } = truncateResult
const contextCondense: ContextCondense = { summary, cost, newContextTokens, prevContextTokens }
await this.say(
@@ -2008,7 +2290,7 @@ export class Task extends EventEmitter implements TaskLike {
}
const messagesSinceLastSummary = getMessagesSinceLastSummary(this.apiConversationHistory)
- const cleanConversationHistory = maybeRemoveImageBlocks(messagesSinceLastSummary, this.api).map(
+ let cleanConversationHistory = maybeRemoveImageBlocks(messagesSinceLastSummary, this.api).map(
({ role, content }) => ({ role, content }),
)
@@ -2024,9 +2306,41 @@ export class Task extends EventEmitter implements TaskLike {
throw new Error("Auto-approval limit reached and user did not approve continuation")
}
+ // Determine GPT‑5 previous_response_id from last persisted assistant turn (if available),
+ // unless a condense just occurred (skip once after condense).
+ let previousResponseId: string | undefined = undefined
+ try {
+ const modelId = this.api.getModel().id
+ if (modelId && modelId.startsWith("gpt-5") && !this.skipPrevResponseIdOnce) {
+ // Find the last assistant message that has a previous_response_id stored
+ const idx = findLastIndex(
+ this.clineMessages,
+ (m) =>
+ m.type === "say" &&
+ (m as any).say === "text" &&
+ (m as any).metadata?.gpt5?.previous_response_id,
+ )
+ if (idx !== -1) {
+ // Use the previous_response_id from the last assistant message for this request
+ previousResponseId = ((this.clineMessages[idx] as any).metadata.gpt5.previous_response_id ||
+ undefined) as string | undefined
+ }
+ }
+ } catch {
+ // non-fatal
+ }
+
const metadata: ApiHandlerCreateMessageMetadata = {
mode: mode,
taskId: this.taskId,
+ ...(previousResponseId ? { previousResponseId } : {}),
+ // If a condense just occurred, explicitly suppress continuity fallback for the next call
+ ...(this.skipPrevResponseIdOnce ? { suppressPreviousResponseId: true } : {}),
+ }
+
+ // Reset skip flag after applying (it only affects the immediate next call)
+ if (this.skipPrevResponseIdOnce) {
+ this.skipPrevResponseIdOnce = false
}
const stream = this.api.createMessage(systemPrompt, cleanConversationHistory, metadata)
@@ -2172,6 +2486,35 @@ export class Task extends EventEmitter implements TaskLike {
}
}
+ /**
+ * Persist GPT-5 per-turn metadata (previous_response_id, instructions, reasoning_summary)
+ * onto the last complete assistant say("text") message.
+ */
+ private async persistGpt5Metadata(reasoningMessage?: string): Promise {
+ try {
+ const modelId = this.api.getModel().id
+ if (!modelId || !modelId.startsWith("gpt-5")) return
+
+ const lastResponseId: string | undefined = (this.api as any)?.getLastResponseId?.()
+ const idx = findLastIndex(
+ this.clineMessages,
+ (m) => m.type === "say" && (m as any).say === "text" && m.partial !== true,
+ )
+ if (idx !== -1) {
+ const msg = this.clineMessages[idx] as any
+ msg.metadata = msg.metadata ?? {}
+ msg.metadata.gpt5 = {
+ ...(msg.metadata.gpt5 ?? {}),
+ previous_response_id: lastResponseId,
+ instructions: this.lastUsedInstructions,
+ reasoning_summary: (reasoningMessage ?? "").trim() || undefined,
+ }
+ }
+ } catch {
+ // Non-fatal error in metadata persistence
+ }
+ }
+
// Getters
public get cwd() {
diff --git a/src/core/task/__tests__/Task.dispose.test.ts b/src/core/task/__tests__/Task.dispose.test.ts
new file mode 100644
index 0000000000..1d93d148a4
--- /dev/null
+++ b/src/core/task/__tests__/Task.dispose.test.ts
@@ -0,0 +1,201 @@
+import { Task } from "../Task"
+import { ClineProvider } from "../../webview/ClineProvider"
+import { ProviderSettings } from "@roo-code/types"
+import { vi, describe, test, expect, beforeEach, afterEach } from "vitest"
+
+// Mock dependencies
+vi.mock("../../webview/ClineProvider")
+vi.mock("../../../integrations/terminal/TerminalRegistry", () => ({
+ TerminalRegistry: {
+ releaseTerminalsForTask: vi.fn(),
+ },
+}))
+vi.mock("../../ignore/RooIgnoreController")
+vi.mock("../../protect/RooProtectedController")
+vi.mock("../../context-tracking/FileContextTracker")
+vi.mock("../../../services/browser/UrlContentFetcher")
+vi.mock("../../../services/browser/BrowserSession")
+vi.mock("../../../integrations/editor/DiffViewProvider")
+vi.mock("../../tools/ToolRepetitionDetector")
+vi.mock("../../../api", () => ({
+ buildApiHandler: vi.fn(() => ({
+ getModel: () => ({ info: {}, id: "test-model" }),
+ })),
+}))
+vi.mock("./AutoApprovalHandler")
+
+// Mock TelemetryService
+vi.mock("@roo-code/telemetry", () => ({
+ TelemetryService: {
+ instance: {
+ captureTaskCreated: vi.fn(),
+ captureTaskRestarted: vi.fn(),
+ },
+ },
+}))
+
+describe("Task dispose method", () => {
+ let mockProvider: any
+ let mockApiConfiguration: ProviderSettings
+ let task: Task
+
+ beforeEach(() => {
+ // Reset all mocks
+ vi.clearAllMocks()
+
+ // Mock provider
+ mockProvider = {
+ context: {
+ globalStorageUri: { fsPath: "/test/path" },
+ },
+ getState: vi.fn().mockResolvedValue({ mode: "code" }),
+ log: vi.fn(),
+ }
+
+ // Mock API configuration
+ mockApiConfiguration = {
+ apiProvider: "anthropic",
+ apiKey: "test-key",
+ } as ProviderSettings
+
+ // Create task instance without starting it
+ task = new Task({
+ provider: mockProvider as ClineProvider,
+ apiConfiguration: mockApiConfiguration,
+ startTask: false,
+ })
+ })
+
+ afterEach(() => {
+ // Clean up
+ if (task && !task.abort) {
+ task.dispose()
+ }
+ })
+
+ test("should remove all event listeners when dispose is called", () => {
+ // Add some event listeners using type assertion to bypass strict typing for testing
+ const listener1 = vi.fn(() => {})
+ const listener2 = vi.fn(() => {})
+ const listener3 = vi.fn((taskId: string) => {})
+
+ // Use type assertion to bypass strict event typing for testing
+ ;(task as any).on("TaskStarted", listener1)
+ ;(task as any).on("TaskAborted", listener2)
+ ;(task as any).on("TaskIdle", listener3)
+
+ // Verify listeners are added
+ expect(task.listenerCount("TaskStarted")).toBe(1)
+ expect(task.listenerCount("TaskAborted")).toBe(1)
+ expect(task.listenerCount("TaskIdle")).toBe(1)
+
+ // Spy on removeAllListeners method
+ const removeAllListenersSpy = vi.spyOn(task, "removeAllListeners")
+
+ // Call dispose
+ task.dispose()
+
+ // Verify removeAllListeners was called
+ expect(removeAllListenersSpy).toHaveBeenCalledOnce()
+
+ // Verify all listeners are removed
+ expect(task.listenerCount("TaskStarted")).toBe(0)
+ expect(task.listenerCount("TaskAborted")).toBe(0)
+ expect(task.listenerCount("TaskIdle")).toBe(0)
+ })
+
+ test("should handle errors when removing event listeners", () => {
+ // Mock removeAllListeners to throw an error
+ const originalRemoveAllListeners = task.removeAllListeners
+ task.removeAllListeners = vi.fn(() => {
+ throw new Error("Test error")
+ })
+
+ // Spy on console.error
+ const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
+
+ // Call dispose - should not throw
+ expect(() => task.dispose()).not.toThrow()
+
+ // Verify error was logged
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Error removing event listeners:", expect.any(Error))
+
+ // Restore
+ task.removeAllListeners = originalRemoveAllListeners
+ consoleErrorSpy.mockRestore()
+ })
+
+ test("should clean up all resources in correct order", () => {
+ const removeAllListenersSpy = vi.spyOn(task, "removeAllListeners")
+ const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {})
+
+ // Call dispose
+ task.dispose()
+
+ // Verify dispose was called and logged
+ expect(consoleLogSpy).toHaveBeenCalledWith(
+ expect.stringContaining(`[Task] disposing task ${task.taskId}.${task.instanceId}`),
+ )
+
+ // Verify removeAllListeners was called first (before other cleanup)
+ expect(removeAllListenersSpy).toHaveBeenCalledOnce()
+
+ // Clean up
+ consoleLogSpy.mockRestore()
+ })
+
+ test("should prevent memory leaks by removing listeners before other cleanup", () => {
+ // Add multiple listeners of different types using type assertion for testing
+ const listeners = {
+ TaskStarted: vi.fn(() => {}),
+ TaskAborted: vi.fn(() => {}),
+ TaskIdle: vi.fn((taskId: string) => {}),
+ TaskActive: vi.fn((taskId: string) => {}),
+ TaskAskResponded: vi.fn(() => {}),
+ Message: vi.fn((data: { action: "created" | "updated"; message: any }) => {}),
+ TaskTokenUsageUpdated: vi.fn((taskId: string, tokenUsage: any) => {}),
+ TaskToolFailed: vi.fn((taskId: string, tool: any, error: string) => {}),
+ TaskUnpaused: vi.fn(() => {}),
+ }
+
+ // Add all listeners using type assertion to bypass strict typing for testing
+ const taskAny = task as any
+ taskAny.on("TaskStarted", listeners.TaskStarted)
+ taskAny.on("TaskAborted", listeners.TaskAborted)
+ taskAny.on("TaskIdle", listeners.TaskIdle)
+ taskAny.on("TaskActive", listeners.TaskActive)
+ taskAny.on("TaskAskResponded", listeners.TaskAskResponded)
+ taskAny.on("Message", listeners.Message)
+ taskAny.on("TaskTokenUsageUpdated", listeners.TaskTokenUsageUpdated)
+ taskAny.on("TaskToolFailed", listeners.TaskToolFailed)
+ taskAny.on("TaskUnpaused", listeners.TaskUnpaused)
+
+ // Verify all listeners are added
+ expect(task.listenerCount("TaskStarted")).toBe(1)
+ expect(task.listenerCount("TaskAborted")).toBe(1)
+ expect(task.listenerCount("TaskIdle")).toBe(1)
+ expect(task.listenerCount("TaskActive")).toBe(1)
+ expect(task.listenerCount("TaskAskResponded")).toBe(1)
+ expect(task.listenerCount("Message")).toBe(1)
+ expect(task.listenerCount("TaskTokenUsageUpdated")).toBe(1)
+ expect(task.listenerCount("TaskToolFailed")).toBe(1)
+ expect(task.listenerCount("TaskUnpaused")).toBe(1)
+
+ // Call dispose
+ task.dispose()
+
+ // Verify all listeners are removed
+ expect(task.listenerCount("TaskStarted")).toBe(0)
+ expect(task.listenerCount("TaskAborted")).toBe(0)
+ expect(task.listenerCount("TaskIdle")).toBe(0)
+ expect(task.listenerCount("TaskActive")).toBe(0)
+ expect(task.listenerCount("TaskAskResponded")).toBe(0)
+ expect(task.listenerCount("Message")).toBe(0)
+ expect(task.listenerCount("TaskTokenUsageUpdated")).toBe(0)
+ expect(task.listenerCount("TaskToolFailed")).toBe(0)
+ expect(task.listenerCount("TaskUnpaused")).toBe(0)
+
+ // Verify total listener count is 0
+ expect(task.eventNames().length).toBe(0)
+ })
+})
diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts
index 9aa5a8d7a8..39df433814 100644
--- a/src/core/task/__tests__/Task.spec.ts
+++ b/src/core/task/__tests__/Task.spec.ts
@@ -1493,5 +1493,125 @@ describe("Cline", () => {
expect(noModelTask.apiConfiguration.apiProvider).toBe("openai")
})
})
+
+ describe("submitUserMessage", () => {
+ it("should always route through webview sendMessage invoke", async () => {
+ const task = new Task({
+ provider: mockProvider,
+ apiConfiguration: mockApiConfig,
+ task: "initial task",
+ startTask: false,
+ })
+
+ // Set up some existing messages to simulate an ongoing conversation
+ task.clineMessages = [
+ {
+ ts: Date.now(),
+ type: "say",
+ say: "text",
+ text: "Initial message",
+ },
+ ]
+
+ // Call submitUserMessage
+ task.submitUserMessage("test message", ["image1.png"])
+
+ // Verify postMessageToWebview was called with sendMessage invoke
+ expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
+ type: "invoke",
+ invoke: "sendMessage",
+ text: "test message",
+ images: ["image1.png"],
+ })
+ })
+
+ it("should handle empty messages gracefully", async () => {
+ const task = new Task({
+ provider: mockProvider,
+ apiConfiguration: mockApiConfig,
+ task: "initial task",
+ startTask: false,
+ })
+
+ // Call with empty text and no images
+ task.submitUserMessage("", [])
+
+ // Should not call postMessageToWebview for empty messages
+ expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled()
+
+ // Call with whitespace only
+ task.submitUserMessage(" ", [])
+ expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled()
+ })
+
+ it("should route through webview for both new and existing tasks", async () => {
+ const task = new Task({
+ provider: mockProvider,
+ apiConfiguration: mockApiConfig,
+ task: "initial task",
+ startTask: false,
+ })
+
+ // Test with no messages (new task scenario)
+ task.clineMessages = []
+ task.submitUserMessage("new task", ["image1.png"])
+
+ expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
+ type: "invoke",
+ invoke: "sendMessage",
+ text: "new task",
+ images: ["image1.png"],
+ })
+
+ // Clear mock
+ mockProvider.postMessageToWebview.mockClear()
+
+ // Test with existing messages (ongoing task scenario)
+ task.clineMessages = [
+ {
+ ts: Date.now(),
+ type: "say",
+ say: "text",
+ text: "Initial message",
+ },
+ ]
+ task.submitUserMessage("follow-up message", ["image2.png"])
+
+ expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
+ type: "invoke",
+ invoke: "sendMessage",
+ text: "follow-up message",
+ images: ["image2.png"],
+ })
+ })
+
+ it("should handle undefined provider gracefully", async () => {
+ const task = new Task({
+ provider: mockProvider,
+ apiConfiguration: mockApiConfig,
+ task: "initial task",
+ startTask: false,
+ })
+
+ // Simulate weakref returning undefined
+ Object.defineProperty(task, "providerRef", {
+ value: { deref: () => undefined },
+ writable: false,
+ configurable: true,
+ })
+
+ // Spy on console.error to verify error is logged
+ const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
+
+ // Should log error but not throw
+ task.submitUserMessage("test message")
+
+ expect(consoleErrorSpy).toHaveBeenCalledWith("[Task#submitUserMessage] Provider reference lost")
+ expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled()
+
+ // Restore console.error
+ consoleErrorSpy.mockRestore()
+ })
+ })
})
})
diff --git a/src/core/tools/ToolRepetitionDetector.ts b/src/core/tools/ToolRepetitionDetector.ts
index 927b031e3b..3662f3250e 100644
--- a/src/core/tools/ToolRepetitionDetector.ts
+++ b/src/core/tools/ToolRepetitionDetector.ts
@@ -39,7 +39,7 @@ export class ToolRepetitionDetector {
if (this.previousToolCallJson === currentToolCallJson) {
this.consecutiveIdenticalToolCallCount++
} else {
- this.consecutiveIdenticalToolCallCount = 1 // Start with 1 for the first occurrence
+ this.consecutiveIdenticalToolCallCount = 0 // Reset to 0 for a new tool
this.previousToolCallJson = currentToolCallJson
}
diff --git a/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts b/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts
index 42041c1a46..e3d89e0372 100644
--- a/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts
+++ b/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts
@@ -32,30 +32,38 @@ describe("ToolRepetitionDetector", () => {
const detector = new ToolRepetitionDetector()
// We'll verify this through behavior in subsequent tests
- // First call (counter = 1)
+ // First call (counter = 0)
const result1 = detector.check(createToolUse("test", "test-tool"))
expect(result1.allowExecution).toBe(true)
- // Second identical call (counter = 2)
+ // Second identical call (counter = 1)
const result2 = detector.check(createToolUse("test", "test-tool"))
expect(result2.allowExecution).toBe(true)
- // Third identical call (counter = 3) reaches the default limit
+ // Third identical call (counter = 2)
const result3 = detector.check(createToolUse("test", "test-tool"))
- expect(result3.allowExecution).toBe(false)
+ expect(result3.allowExecution).toBe(true)
+
+ // Fourth identical call (counter = 3) reaches the default limit
+ const result4 = detector.check(createToolUse("test", "test-tool"))
+ expect(result4.allowExecution).toBe(false)
})
it("should use the custom limit when provided", () => {
const customLimit = 2
const detector = new ToolRepetitionDetector(customLimit)
- // First call (counter = 1)
+ // First call (counter = 0)
const result1 = detector.check(createToolUse("test", "test-tool"))
expect(result1.allowExecution).toBe(true)
- // Second identical call (counter = 2) reaches the custom limit
+ // Second identical call (counter = 1)
const result2 = detector.check(createToolUse("test", "test-tool"))
- expect(result2.allowExecution).toBe(false)
+ expect(result2.allowExecution).toBe(true)
+
+ // Third identical call (counter = 2) reaches the custom limit
+ const result3 = detector.check(createToolUse("test", "test-tool"))
+ expect(result3.allowExecution).toBe(false)
})
})
@@ -97,17 +105,21 @@ describe("ToolRepetitionDetector", () => {
it("should allow execution when repetition is below limit and block when limit reached", () => {
const detector = new ToolRepetitionDetector(3)
- // First call (counter = 1)
+ // First call (counter = 0)
const result1 = detector.check(createToolUse("repeat", "repeat-tool"))
expect(result1.allowExecution).toBe(true)
- // Second identical call (counter = 2)
+ // Second identical call (counter = 1)
const result2 = detector.check(createToolUse("repeat", "repeat-tool"))
expect(result2.allowExecution).toBe(true)
- // Third identical call (counter = 3) reaches limit
+ // Third identical call (counter = 2)
const result3 = detector.check(createToolUse("repeat", "repeat-tool"))
- expect(result3.allowExecution).toBe(false)
+ expect(result3.allowExecution).toBe(true)
+
+ // Fourth identical call (counter = 3) reaches limit
+ const result4 = detector.check(createToolUse("repeat", "repeat-tool"))
+ expect(result4.allowExecution).toBe(false)
})
})
@@ -116,13 +128,16 @@ describe("ToolRepetitionDetector", () => {
it("should block execution when repetition reaches the limit", () => {
const detector = new ToolRepetitionDetector(3)
- // First call (counter = 1)
+ // First call (counter = 0)
detector.check(createToolUse("repeat", "repeat-tool"))
- // Second identical call (counter = 2)
+ // Second identical call (counter = 1)
detector.check(createToolUse("repeat", "repeat-tool"))
- // Third identical call (counter = 3) - should reach limit
+ // Third identical call (counter = 2)
+ detector.check(createToolUse("repeat", "repeat-tool"))
+
+ // Fourth identical call (counter = 3) - should reach limit
const result = detector.check(createToolUse("repeat", "repeat-tool"))
expect(result.allowExecution).toBe(false)
@@ -136,6 +151,7 @@ describe("ToolRepetitionDetector", () => {
// Reach the limit
detector.check(createToolUse("repeat", "repeat-tool"))
+ detector.check(createToolUse("repeat", "repeat-tool"))
const limitResult = detector.check(createToolUse("repeat", "repeat-tool")) // This reaches limit
expect(limitResult.allowExecution).toBe(false)
@@ -152,6 +168,7 @@ describe("ToolRepetitionDetector", () => {
// Reach the limit with a specific tool
detector.check(createToolUse("problem", "problem-tool"))
+ detector.check(createToolUse("problem", "problem-tool"))
const limitResult = detector.check(createToolUse("problem", "problem-tool")) // This reaches limit
expect(limitResult.allowExecution).toBe(false)
@@ -165,13 +182,17 @@ describe("ToolRepetitionDetector", () => {
// Reach the limit
detector.check(createToolUse("repeat", "repeat-tool"))
+ detector.check(createToolUse("repeat", "repeat-tool"))
const limitResult = detector.check(createToolUse("repeat", "repeat-tool")) // This reaches limit
expect(limitResult.allowExecution).toBe(false)
// First call after reset
detector.check(createToolUse("repeat", "repeat-tool"))
- // Second identical call (counter = 2) should reach limit again
+ // Second call after reset
+ detector.check(createToolUse("repeat", "repeat-tool"))
+
+ // Third identical call (counter = 2) should reach limit again
const result = detector.check(createToolUse("repeat", "repeat-tool"))
expect(result.allowExecution).toBe(false)
expect(result.askUser).toBeDefined()
@@ -186,6 +207,7 @@ describe("ToolRepetitionDetector", () => {
// Reach the limit
detector.check(createToolUse("test", toolName))
+ detector.check(createToolUse("test", toolName))
const result = detector.check(createToolUse("test", toolName))
expect(result.allowExecution).toBe(false)
@@ -201,7 +223,8 @@ describe("ToolRepetitionDetector", () => {
// Create an empty tool call - a tool with no parameters
// Use the empty tool directly in the check calls
detector.check(createToolUse("empty-tool", "empty-tool"))
- const result = detector.check(createToolUse("empty-tool"))
+ detector.check(createToolUse("empty-tool", "empty-tool"))
+ const result = detector.check(createToolUse("empty-tool", "empty-tool"))
expect(result.allowExecution).toBe(false)
expect(result.askUser).toBeDefined()
@@ -210,7 +233,7 @@ describe("ToolRepetitionDetector", () => {
it("should handle different tool names with identical serialized JSON", () => {
const detector = new ToolRepetitionDetector(2)
- // First, call with tool-name-1 twice to set up the counter
+ // First, call with tool-name-1 to set up the counter
const toolUse1 = createToolUse("tool-name-1", "tool-name-1", { param: "value" })
detector.check(toolUse1)
@@ -223,21 +246,25 @@ describe("ToolRepetitionDetector", () => {
;(detector as any).serializeToolUse = (tool: ToolUse) => {
// Use string comparison for the name since it's technically an enum
if (String(tool.name) === "tool-name-2") {
- return (detector as any).serializeToolUse(toolUse1) // Return the same JSON as toolUse1
+ return originalSerialize.call(detector, toolUse1) // Return the same JSON as toolUse1
}
- return originalSerialize(tool)
+ return originalSerialize.call(detector, tool)
}
- // This should detect as a repetition now
- const result = detector.check(toolUse2)
+ // Second call - this should be considered identical due to our mock
+ const result2 = detector.check(toolUse2)
+ expect(result2.allowExecution).toBe(true) // Still allowed (counter = 1)
+
+ // Third call - should be blocked (limit is 2)
+ const result3 = detector.check(toolUse2)
// Restore the original method
;(detector as any).serializeToolUse = originalSerialize
// Since we're directly manipulating the internal state for testing,
- // we still expect it to consider this a repetition
- expect(result.allowExecution).toBe(false)
- expect(result.askUser).toBeDefined()
+ // we expect it to consider this a repetition
+ expect(result3.allowExecution).toBe(false)
+ expect(result3.askUser).toBeDefined()
})
it("should treat tools with same parameters in different order as identical", () => {
@@ -247,11 +274,13 @@ describe("ToolRepetitionDetector", () => {
const toolUse1 = createToolUse("same-tool", "same-tool", { a: "1", b: "2", c: "3" })
detector.check(toolUse1)
- // Create tool with same parameters but in different order
+ // Second call with same parameters but in different order
const toolUse2 = createToolUse("same-tool", "same-tool", { c: "3", a: "1", b: "2" })
+ detector.check(toolUse2)
- // This should still detect as a repetition due to canonical JSON with sorted keys
- const result = detector.check(toolUse2)
+ // Third call - should be blocked (limit is 2)
+ const toolUse3 = createToolUse("same-tool", "same-tool", { b: "2", c: "3", a: "1" })
+ const result = detector.check(toolUse3)
// Since parameters are sorted alphabetically in the serialized JSON,
// these should be considered identical
@@ -262,46 +291,58 @@ describe("ToolRepetitionDetector", () => {
// ===== Explicit Nth Call Blocking tests =====
describe("explicit Nth call blocking behavior", () => {
- it("should block on the 1st call for limit 1", () => {
+ it("should allow the 1st call but block on the 2nd call for limit 1", () => {
const detector = new ToolRepetitionDetector(1)
- // First call (counter = 1) should be blocked
- const result = detector.check(createToolUse("tool", "tool-name"))
-
- expect(result.allowExecution).toBe(false)
- expect(result.askUser).toBeDefined()
- })
-
- it("should block on the 2nd call for limit 2", () => {
- const detector = new ToolRepetitionDetector(2)
-
- // First call (counter = 1)
+ // First call (counter = 0) should be allowed
const result1 = detector.check(createToolUse("tool", "tool-name"))
expect(result1.allowExecution).toBe(true)
+ expect(result1.askUser).toBeUndefined()
- // Second call (counter = 2) should be blocked
+ // Second identical call (counter = 1) should be blocked
const result2 = detector.check(createToolUse("tool", "tool-name"))
expect(result2.allowExecution).toBe(false)
expect(result2.askUser).toBeDefined()
})
- it("should block on the 3rd call for limit 3 (default)", () => {
- const detector = new ToolRepetitionDetector(3)
+ it("should allow first 2 calls but block on the 3rd call for limit 2", () => {
+ const detector = new ToolRepetitionDetector(2)
- // First call (counter = 1)
+ // First call (counter = 0)
const result1 = detector.check(createToolUse("tool", "tool-name"))
expect(result1.allowExecution).toBe(true)
- // Second call (counter = 2)
+ // Second identical call (counter = 1)
const result2 = detector.check(createToolUse("tool", "tool-name"))
expect(result2.allowExecution).toBe(true)
- // Third call (counter = 3) should be blocked
+ // Third identical call (counter = 2) should be blocked
const result3 = detector.check(createToolUse("tool", "tool-name"))
expect(result3.allowExecution).toBe(false)
expect(result3.askUser).toBeDefined()
})
+ it("should allow first 3 calls but block on the 4th call for limit 3 (default)", () => {
+ const detector = new ToolRepetitionDetector(3)
+
+ // First call (counter = 0)
+ const result1 = detector.check(createToolUse("tool", "tool-name"))
+ expect(result1.allowExecution).toBe(true)
+
+ // Second identical call (counter = 1)
+ const result2 = detector.check(createToolUse("tool", "tool-name"))
+ expect(result2.allowExecution).toBe(true)
+
+ // Third identical call (counter = 2)
+ const result3 = detector.check(createToolUse("tool", "tool-name"))
+ expect(result3.allowExecution).toBe(true)
+
+ // Fourth identical call (counter = 3) should be blocked
+ const result4 = detector.check(createToolUse("tool", "tool-name"))
+ expect(result4.allowExecution).toBe(false)
+ expect(result4.askUser).toBeDefined()
+ })
+
it("should never block when limit is 0 (unlimited)", () => {
const detector = new ToolRepetitionDetector(0)
@@ -318,18 +359,18 @@ describe("ToolRepetitionDetector", () => {
const detector5 = new ToolRepetitionDetector(5)
const tool = createToolUse("tool", "tool-name")
- // First 4 calls should be allowed
- for (let i = 0; i < 4; i++) {
+ // First 5 calls should be allowed
+ for (let i = 0; i < 5; i++) {
const result = detector5.check(tool)
expect(result.allowExecution).toBe(true)
expect(result.askUser).toBeUndefined()
}
- // 5th call should be blocked
- const result5 = detector5.check(tool)
- expect(result5.allowExecution).toBe(false)
- expect(result5.askUser).toBeDefined()
- expect(result5.askUser?.messageKey).toBe("mistake_limit_reached")
+ // 6th call should be blocked
+ const result6 = detector5.check(tool)
+ expect(result6.allowExecution).toBe(false)
+ expect(result6.askUser).toBeDefined()
+ expect(result6.askUser?.messageKey).toBe("mistake_limit_reached")
})
it("should reset counter after blocking and allow new attempts", () => {
@@ -339,7 +380,10 @@ describe("ToolRepetitionDetector", () => {
// First call allowed
expect(detector.check(tool).allowExecution).toBe(true)
- // Second call should block (limit is 2)
+ // Second call allowed
+ expect(detector.check(tool).allowExecution).toBe(true)
+
+ // Third call should block (limit is 2)
const blocked = detector.check(tool)
expect(blocked.allowExecution).toBe(false)
diff --git a/src/core/tools/__tests__/multiApplyDiffTool.spec.ts b/src/core/tools/__tests__/multiApplyDiffTool.spec.ts
new file mode 100644
index 0000000000..0bdedb9cd5
--- /dev/null
+++ b/src/core/tools/__tests__/multiApplyDiffTool.spec.ts
@@ -0,0 +1,401 @@
+import { applyDiffTool } from "../multiApplyDiffTool"
+import { EXPERIMENT_IDS } from "../../../shared/experiments"
+import * as fs from "fs/promises"
+import * as fileUtils from "../../../utils/fs"
+import * as pathUtils from "../../../utils/path"
+
+// Mock dependencies
+vi.mock("fs/promises")
+vi.mock("../../../utils/fs")
+vi.mock("../../../utils/path")
+vi.mock("../../../utils/xml")
+vi.mock("../applyDiffTool", () => ({
+ applyDiffToolLegacy: vi.fn(),
+}))
+
+// Mock TelemetryService
+vi.mock("@roo-code/telemetry", () => ({
+ TelemetryService: {
+ get instance() {
+ return {
+ trackEvent: vi.fn(),
+ trackError: vi.fn(),
+ captureDiffApplicationError: vi.fn(),
+ }
+ },
+ },
+}))
+
+describe("multiApplyDiffTool", () => {
+ let mockCline: any
+ let mockBlock: any
+ let mockAskApproval: any
+ let mockHandleError: any
+ let mockPushToolResult: any
+ let mockRemoveClosingTag: any
+ let mockProvider: any
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+
+ mockProvider = {
+ getState: vi.fn().mockResolvedValue({
+ experiments: {
+ [EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF]: true,
+ },
+ diagnosticsEnabled: true,
+ writeDelayMs: 0,
+ }),
+ }
+
+ mockCline = {
+ providerRef: {
+ deref: vi.fn().mockReturnValue(mockProvider),
+ },
+ cwd: "/test",
+ taskId: "test-task",
+ consecutiveMistakeCount: 0,
+ consecutiveMistakeCountForApplyDiff: new Map(),
+ recordToolError: vi.fn(),
+ say: vi.fn().mockResolvedValue(undefined),
+ sayAndCreateMissingParamError: vi.fn().mockResolvedValue("Missing parameter error"),
+ ask: vi.fn().mockResolvedValue({ response: "yesButtonClicked", text: "", images: [] }),
+ diffStrategy: {
+ applyDiff: vi.fn().mockResolvedValue({
+ success: true,
+ content: "modified content",
+ }),
+ getProgressStatus: vi.fn(),
+ },
+ diffViewProvider: {
+ reset: vi.fn().mockResolvedValue(undefined),
+ editType: undefined,
+ originalContent: undefined,
+ open: vi.fn().mockResolvedValue(undefined),
+ update: vi.fn().mockResolvedValue(undefined),
+ scrollToFirstDiff: vi.fn(),
+ saveDirectly: vi.fn().mockResolvedValue(undefined),
+ saveChanges: vi.fn().mockResolvedValue(undefined),
+ pushToolWriteResult: vi.fn().mockResolvedValue("File modified successfully"),
+ },
+ api: {
+ getModel: vi.fn().mockReturnValue({ id: "test-model" }),
+ },
+ rooIgnoreController: {
+ validateAccess: vi.fn().mockReturnValue(true),
+ },
+ rooProtectedController: {
+ isWriteProtected: vi.fn().mockReturnValue(false),
+ },
+ fileContextTracker: {
+ trackFileContext: vi.fn().mockResolvedValue(undefined),
+ },
+ didEditFile: false,
+ } as any
+
+ mockAskApproval = vi.fn().mockResolvedValue(true)
+ mockHandleError = vi.fn()
+ mockPushToolResult = vi.fn()
+ mockRemoveClosingTag = vi.fn((tag, value) => value)
+
+ // Mock file system operations
+ ;(fileUtils.fileExistsAtPath as any).mockResolvedValue(true)
+ ;(fs.readFile as any).mockResolvedValue("original content")
+ ;(pathUtils.getReadablePath as any).mockImplementation((cwd: string, path: string) => path)
+ })
+
+ describe("Early content validation", () => {
+ it("should filter out non-string content at parse time", async () => {
+ mockBlock = {
+ params: {
+ args: `
+ test.ts
+
+ valid string content
+
+ `,
+ },
+ partial: false,
+ }
+
+ // Mock parseXml to return mixed content types
+ const parseXml = await import("../../../utils/xml")
+ ;(parseXml.parseXml as any).mockReturnValue({
+ file: {
+ path: "test.ts",
+ diff: [
+ { content: "<<<<<<< SEARCH\ntest\n=======\nreplaced\n>>>>>>> REPLACE" },
+ { content: null },
+ { content: undefined },
+ { content: 42 },
+ { content: "" }, // Empty string should also be filtered
+ { content: "<<<<<<< SEARCH\nmore\n=======\nchanges\n>>>>>>> REPLACE" },
+ ],
+ },
+ })
+
+ await applyDiffTool(
+ mockCline,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should complete without error and only process valid string content
+ expect(mockPushToolResult).toHaveBeenCalled()
+ expect(mockHandleError).not.toHaveBeenCalled()
+
+ // Verify that only valid diffs were processed
+ const resultCall = mockPushToolResult.mock.calls[0][0]
+ // Should not include the single block notice since we have 2 valid blocks
+ expect(resultCall).not.toContain("Making multiple related changes")
+ })
+ })
+
+ describe("SEARCH block counting with non-string content", () => {
+ it("should handle diffItem.content being undefined", async () => {
+ mockBlock = {
+ params: {
+ path: "test.ts",
+ diff: undefined, // This will result in undefined content
+ },
+ partial: false,
+ }
+
+ await applyDiffTool(
+ mockCline,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should complete without throwing an error
+ expect(mockPushToolResult).toHaveBeenCalled()
+ expect(mockHandleError).not.toHaveBeenCalled()
+ })
+
+ it("should handle diffItem.content being null", async () => {
+ mockBlock = {
+ params: {
+ args: `
+ test.ts
+
+
+
+ `,
+ },
+ partial: false,
+ }
+
+ // Mock parseXml to return null content
+ const parseXml = await import("../../../utils/xml")
+ ;(parseXml.parseXml as any).mockReturnValue({
+ file: {
+ path: "test.ts",
+ diff: {
+ content: null,
+ },
+ },
+ })
+
+ await applyDiffTool(
+ mockCline,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should complete without throwing an error
+ expect(mockPushToolResult).toHaveBeenCalled()
+ expect(mockHandleError).not.toHaveBeenCalled()
+ })
+
+ it("should handle diffItem.content being a number", async () => {
+ mockBlock = {
+ params: {
+ args: `
+ test.ts
+
+ 123
+
+ `,
+ },
+ partial: false,
+ }
+
+ // Mock parseXml to return number content
+ const parseXml = await import("../../../utils/xml")
+ ;(parseXml.parseXml as any).mockReturnValue({
+ file: {
+ path: "test.ts",
+ diff: {
+ content: 123, // Number instead of string
+ },
+ },
+ })
+
+ await applyDiffTool(
+ mockCline,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should complete without throwing an error
+ expect(mockPushToolResult).toHaveBeenCalled()
+ expect(mockHandleError).not.toHaveBeenCalled()
+ })
+
+ it("should correctly count SEARCH blocks when content is a valid string", async () => {
+ const diffContent = `<<<<<<< SEARCH
+old content
+=======
+new content
+>>>>>>> REPLACE
+
+<<<<<<< SEARCH
+another old content
+=======
+another new content
+>>>>>>> REPLACE`
+
+ mockBlock = {
+ params: {
+ path: "test.ts",
+ diff: diffContent,
+ },
+ partial: false,
+ }
+
+ await applyDiffTool(
+ mockCline,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should complete successfully
+ expect(mockPushToolResult).toHaveBeenCalled()
+ const resultCall = mockPushToolResult.mock.calls[0][0]
+ // Should not include the single block notice since we have 2 blocks
+ expect(resultCall).not.toContain("Making multiple related changes")
+ })
+
+ it("should show single block notice when only one SEARCH block exists", async () => {
+ const diffContent = `<<<<<<< SEARCH
+old content
+=======
+new content
+>>>>>>> REPLACE`
+
+ mockBlock = {
+ params: {
+ path: "test.ts",
+ diff: diffContent,
+ },
+ partial: false,
+ }
+
+ await applyDiffTool(
+ mockCline,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should complete successfully
+ expect(mockPushToolResult).toHaveBeenCalled()
+ const resultCall = mockPushToolResult.mock.calls[0][0]
+ // Should include the single block notice
+ expect(resultCall).toContain("Making multiple related changes")
+ })
+ })
+
+ describe("Edge cases for diff content", () => {
+ it("should handle empty diff array", async () => {
+ mockBlock = {
+ params: {
+ args: `
+ test.ts
+
+ `,
+ },
+ partial: false,
+ }
+
+ const parseXml = await import("../../../utils/xml")
+ ;(parseXml.parseXml as any).mockReturnValue({
+ file: {
+ path: "test.ts",
+ diff: [],
+ },
+ })
+
+ await applyDiffTool(
+ mockCline,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should complete without error
+ expect(mockPushToolResult).toHaveBeenCalled()
+ expect(mockHandleError).not.toHaveBeenCalled()
+ })
+
+ it("should handle mixed content types in diff array", async () => {
+ mockBlock = {
+ params: {
+ args: `
+ test.ts
+
+ valid string content
+
+ `,
+ },
+ partial: false,
+ }
+
+ const parseXml = await import("../../../utils/xml")
+ ;(parseXml.parseXml as any).mockReturnValue({
+ file: {
+ path: "test.ts",
+ diff: [
+ { content: "<<<<<<< SEARCH\ntest\n=======\nreplaced\n>>>>>>> REPLACE" },
+ { content: null },
+ { content: undefined },
+ { content: 42 },
+ { content: "<<<<<<< SEARCH\nmore\n=======\nchanges\n>>>>>>> REPLACE" },
+ ],
+ },
+ })
+
+ await applyDiffTool(
+ mockCline,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should complete without error and count only valid string SEARCH blocks
+ expect(mockPushToolResult).toHaveBeenCalled()
+ expect(mockHandleError).not.toHaveBeenCalled()
+ })
+ })
+})
diff --git a/src/core/tools/codebaseSearchTool.ts b/src/core/tools/codebaseSearchTool.ts
index 236b066306..263fa8755e 100644
--- a/src/core/tools/codebaseSearchTool.ts
+++ b/src/core/tools/codebaseSearchTool.ts
@@ -17,7 +17,7 @@ export async function codebaseSearchTool(
removeClosingTag: RemoveClosingTag,
) {
const toolName = "codebase_search"
- const workspacePath = getWorkspacePath()
+ const workspacePath = cline.cwd && cline.cwd.trim() !== "" ? cline.cwd : getWorkspacePath()
if (!workspacePath) {
// This case should ideally not happen if Cline is initialized correctly
diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts
index db514d2b64..50695b1da7 100644
--- a/src/core/tools/multiApplyDiffTool.ts
+++ b/src/core/tools/multiApplyDiffTool.ts
@@ -12,7 +12,7 @@ import { formatResponse } from "../prompts/responses"
import { fileExistsAtPath } from "../../utils/fs"
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
import { unescapeHtmlEntities } from "../../utils/text-normalization"
-import { parseXml } from "../../utils/xml"
+import { parseXmlForDiff } from "../../utils/xml"
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
import { applyDiffToolLegacy } from "./applyDiffTool"
@@ -108,7 +108,10 @@ export async function applyDiffTool(
if (argsXmlTag) {
// Parse file entries from XML (new way)
try {
- const parsed = parseXml(argsXmlTag, ["file.diff.content"]) as ParsedXmlResult
+ // IMPORTANT: We use parseXmlForDiff here instead of parseXml to prevent HTML entity decoding
+ // This ensures exact character matching when comparing parsed content against original file content
+ // Without this, special characters like & would be decoded to & causing diff mismatches
+ const parsed = parseXmlForDiff(argsXmlTag, ["file.diff.content"]) as ParsedXmlResult
const files = Array.isArray(parsed.file) ? parsed.file : [parsed.file].filter(Boolean)
for (const file of files) {
@@ -132,13 +135,17 @@ export async function applyDiffTool(
let diffContent: string
let startLine: number | undefined
- diffContent = diff.content
+ // Ensure content is a string before storing it
+ diffContent = typeof diff.content === "string" ? diff.content : ""
startLine = diff.start_line ? parseInt(diff.start_line) : undefined
- operationsMap[filePath].diff.push({
- content: diffContent,
- startLine,
- })
+ // Only add to operations if we have valid content
+ if (diffContent) {
+ operationsMap[filePath].diff.push({
+ content: diffContent,
+ startLine,
+ })
+ }
}
}
} catch (error) {
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 274060a19b..6622b29891 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -17,7 +17,6 @@ import {
type ProviderSettings,
type RooCodeSettings,
type ProviderSettingsEntry,
- type ProviderSettingsWithId,
type TelemetryProperties,
type TelemetryPropertiesProvider,
type CodeActionId,
@@ -25,17 +24,15 @@ import {
type TerminalActionId,
type TerminalActionPromptType,
type HistoryItem,
- type CloudUserInfo,
RooCodeEventName,
requestyDefaultModelId,
openRouterDefaultModelId,
glamaDefaultModelId,
- ORGANIZATION_ALLOW_ALL,
DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
DEFAULT_WRITE_DELAY_MS,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
-import { CloudService, getRooCodeApiUrl } from "@roo-code/cloud"
+import { type CloudUserInfo, CloudService, ORGANIZATION_ALLOW_ALL, getRooCodeApiUrl } from "@roo-code/cloud"
import { Package } from "../../shared/package"
import { findLast } from "../../shared/array"
@@ -66,6 +63,7 @@ import { fileExistsAtPath } from "../../utils/fs"
import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
import { getWorkspaceGitInfo } from "../../utils/git"
import { getWorkspacePath } from "../../utils/path"
+import { isRemoteControlEnabled } from "../../utils/remoteControl"
import { setPanel } from "../../activate/registerCommands"
@@ -112,6 +110,8 @@ export class ClineProvider
protected mcpHub?: McpHub // Change from private to protected
private marketplaceManager: MarketplaceManager
private mdmService?: MdmService
+ private taskCreationCallback: (task: Task) => void
+ private taskEventListeners: WeakMap void>> = new WeakMap()
public isViewLaunched = false
public settingsImportedAt?: number
@@ -161,12 +161,66 @@ export class ClineProvider
this.marketplaceManager = new MarketplaceManager(this.context, this.customModesManager)
+ this.taskCreationCallback = (instance: Task) => {
+ this.emit(RooCodeEventName.TaskCreated, instance)
+
+ // Create named listener functions so we can remove them later.
+ const onTaskStarted = () => this.emit(RooCodeEventName.TaskStarted, instance.taskId)
+ const onTaskCompleted = (taskId: string, tokenUsage: any, toolUsage: any) =>
+ this.emit(RooCodeEventName.TaskCompleted, taskId, tokenUsage, toolUsage)
+ const onTaskAborted = () => this.emit(RooCodeEventName.TaskAborted, instance.taskId)
+ const onTaskFocused = () => this.emit(RooCodeEventName.TaskFocused, instance.taskId)
+ const onTaskUnfocused = () => this.emit(RooCodeEventName.TaskUnfocused, instance.taskId)
+ const onTaskActive = (taskId: string) => this.emit(RooCodeEventName.TaskActive, taskId)
+ const onTaskIdle = (taskId: string) => this.emit(RooCodeEventName.TaskIdle, taskId)
+
+ // Attach the listeners.
+ instance.on(RooCodeEventName.TaskStarted, onTaskStarted)
+ instance.on(RooCodeEventName.TaskCompleted, onTaskCompleted)
+ instance.on(RooCodeEventName.TaskAborted, onTaskAborted)
+ instance.on(RooCodeEventName.TaskFocused, onTaskFocused)
+ instance.on(RooCodeEventName.TaskUnfocused, onTaskUnfocused)
+ instance.on(RooCodeEventName.TaskActive, onTaskActive)
+ instance.on(RooCodeEventName.TaskIdle, onTaskIdle)
+
+ // Store the cleanup functions for later removal.
+ this.taskEventListeners.set(instance, [
+ () => instance.off(RooCodeEventName.TaskStarted, onTaskStarted),
+ () => instance.off(RooCodeEventName.TaskCompleted, onTaskCompleted),
+ () => instance.off(RooCodeEventName.TaskAborted, onTaskAborted),
+ () => instance.off(RooCodeEventName.TaskFocused, onTaskFocused),
+ () => instance.off(RooCodeEventName.TaskUnfocused, onTaskUnfocused),
+ () => instance.off(RooCodeEventName.TaskActive, onTaskActive),
+ () => instance.off(RooCodeEventName.TaskIdle, onTaskIdle),
+ ])
+ }
+
// Initialize Roo Code Cloud profile sync.
this.initializeCloudProfileSync().catch((error) => {
this.log(`Failed to initialize cloud profile sync: ${error}`)
})
}
+ /**
+ * Override EventEmitter's on method to match TaskProviderLike interface
+ */
+ override on(
+ event: K,
+ listener: (...args: TaskProviderEvents[K]) => void | Promise,
+ ): this {
+ return super.on(event, listener as any)
+ }
+
+ /**
+ * Override EventEmitter's off method to match TaskProviderLike interface
+ */
+ override off(
+ event: K,
+ listener: (...args: TaskProviderEvents[K]) => void | Promise,
+ ): this {
+ return super.off(event, listener as any)
+ }
+
/**
* Initialize cloud profile synchronization
*/
@@ -296,6 +350,14 @@ export class ClineProvider
task.emit(RooCodeEventName.TaskUnfocused)
+ // Remove event listeners before clearing the reference.
+ const cleanupFunctions = this.taskEventListeners.get(task)
+
+ if (cleanupFunctions) {
+ cleanupFunctions.forEach((cleanup) => cleanup())
+ this.taskEventListeners.delete(task)
+ }
+
// Make sure no reference kept, once promises end it will be
// garbage collected.
task = undefined
@@ -498,18 +560,16 @@ export class ClineProvider
this.log("Resolving webview view")
this.view = webviewView
-
- // Set panel reference according to webview type
const inTabMode = "onDidChangeViewState" in webviewView
+
if (inTabMode) {
- // Tag page type
setPanel(webviewView, "tab")
} else if ("onDidChangeVisibility" in webviewView) {
- // Sidebar Type
setPanel(webviewView, "sidebar")
}
- // Initialize out-of-scope variables that need to receive persistent global state values
+ // Initialize out-of-scope variables that need to receive persistent
+ // global state values.
this.getState().then(
({
terminalShellIntegrationTimeout = Terminal.defaultShellIntegrationTimeout,
@@ -532,18 +592,15 @@ export class ClineProvider
},
)
- // Initialize tts enabled state
this.getState().then(({ ttsEnabled }) => {
setTtsEnabled(ttsEnabled ?? false)
})
- // Initialize tts speed state
this.getState().then(({ ttsSpeed }) => {
setTtsSpeed(ttsSpeed ?? 1)
})
webviewView.webview.options = {
- // Allow scripts in the webview
enableScripts: true,
localResourceRoots: [this.contextProxy.extensionUri],
}
@@ -554,32 +611,31 @@ export class ClineProvider
: this.getHtmlContent(webviewView.webview)
// Sets up an event listener to listen for messages passed from the webview view context
- // and executes code based on the message that is received
+ // and executes code based on the message that is received.
this.setWebviewMessageListener(webviewView.webview)
- // Initialize code index status subscription for the current workspace
+ // Initialize code index status subscription for the current workspace.
this.updateCodeIndexStatusSubscription()
- // Listen for active editor changes to update code index status for the current workspace
+ // Listen for active editor changes to update code index status for the
+ // current workspace.
const activeEditorSubscription = vscode.window.onDidChangeActiveTextEditor(() => {
- // Update subscription when workspace might have changed
+ // Update subscription when workspace might have changed.
this.updateCodeIndexStatusSubscription()
})
this.webviewDisposables.push(activeEditorSubscription)
- // Logs show up in bottom panel > Debug Console
- //console.log("registering listener")
-
- // Listen for when the panel becomes visible
+ // Listen for when the panel becomes visible.
// https://github.com/microsoft/vscode-discussions/discussions/840
if ("onDidChangeViewState" in webviewView) {
- // WebviewView and WebviewPanel have all the same properties except for this visibility listener
- // panel
+ // WebviewView and WebviewPanel have all the same properties except
+ // for this visibility listener panel.
const viewStateDisposable = webviewView.onDidChangeViewState(() => {
if (this.view?.visible) {
this.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
}
})
+
this.webviewDisposables.push(viewStateDisposable)
} else if ("onDidChangeVisibility" in webviewView) {
// sidebar
@@ -588,6 +644,7 @@ export class ClineProvider
this.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
}
})
+
this.webviewDisposables.push(visibilityDisposable)
}
@@ -652,6 +709,8 @@ export class ClineProvider
enableCheckpoints,
fuzzyMatchThreshold,
experiments,
+ cloudUserInfo,
+ remoteControlEnabled,
} = await this.getState()
if (!ProfileValidator.isProfileAllowed(apiConfiguration, organizationAllowList)) {
@@ -671,7 +730,8 @@ export class ClineProvider
rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined,
parentTask,
taskNumber: this.clineStack.length + 1,
- onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance),
+ onCreated: this.taskCreationCallback,
+ enableTaskBridge: isRemoteControlEnabled(cloudUserInfo, remoteControlEnabled),
...options,
})
@@ -736,8 +796,13 @@ export class ClineProvider
enableCheckpoints,
fuzzyMatchThreshold,
experiments,
+ cloudUserInfo,
+ remoteControlEnabled,
} = await this.getState()
+ // Determine if TaskBridge should be enabled
+ const enableTaskBridge = isRemoteControlEnabled(cloudUserInfo, remoteControlEnabled)
+
const task = new Task({
provider: this,
apiConfiguration,
@@ -750,7 +815,8 @@ export class ClineProvider
rootTask: historyItem.rootTask,
parentTask: historyItem.parentTask,
taskNumber: historyItem.number,
- onCreated: (instance) => this.emit(RooCodeEventName.TaskCreated, instance),
+ onCreated: this.taskCreationCallback,
+ enableTaskBridge,
})
await this.addClineToStack(task)
@@ -767,8 +833,8 @@ export class ClineProvider
}
private async getHMRHtmlContent(webview: vscode.Webview): Promise {
- // Try to read the port from the file
- let localPort = "5173" // Default fallback
+ let localPort = "5173"
+
try {
const fs = require("fs")
const path = require("path")
@@ -784,7 +850,6 @@ export class ClineProvider
}
} catch (err) {
console.error("[ClineProvider:Vite] Failed to read Vite port file:", err)
- // Continue with default port if file reading fails
}
const localServerUrl = `localhost:${localPort}`
@@ -794,7 +859,6 @@ export class ClineProvider
await axios.get(`http://${localServerUrl}`)
} catch (error) {
vscode.window.showErrorMessage(t("common:errors.hmr_not_running"))
-
return this.getHtmlContent(webview)
}
@@ -1629,6 +1693,7 @@ export class ClineProvider
includeDiagnosticMessages,
maxDiagnosticMessages,
includeTaskHistoryInEnhance,
+ remoteControlEnabled,
} = await this.getState()
const telemetryKey = process.env.POSTHOG_API_KEY
@@ -1756,6 +1821,7 @@ export class ClineProvider
includeDiagnosticMessages: includeDiagnosticMessages ?? true,
maxDiagnosticMessages: maxDiagnosticMessages ?? 50,
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? false,
+ remoteControlEnabled: remoteControlEnabled ?? false,
}
}
@@ -1943,6 +2009,8 @@ export class ClineProvider
maxDiagnosticMessages: stateValues.maxDiagnosticMessages ?? 50,
// Add includeTaskHistoryInEnhance setting
includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? false,
+ // Add remoteControlEnabled setting
+ remoteControlEnabled: stateValues.remoteControlEnabled ?? false,
}
}
@@ -2055,6 +2123,59 @@ export class ClineProvider
return true
}
+ public async handleRemoteControlToggle(enabled: boolean) {
+ const { CloudService: CloudServiceImport, ExtensionBridgeService } = await import("@roo-code/cloud")
+
+ const userInfo = CloudServiceImport.instance.getUserInfo()
+
+ const bridgeConfig = await CloudServiceImport.instance.cloudAPI?.bridgeConfig().catch(() => undefined)
+
+ if (!bridgeConfig) {
+ this.log("[ClineProvider#handleRemoteControlToggle] Failed to get bridge config")
+ return
+ }
+
+ await ExtensionBridgeService.handleRemoteControlState(
+ userInfo,
+ enabled,
+ { ...bridgeConfig, provider: this, sessionId: vscode.env.sessionId },
+ (message: string) => this.log(message),
+ )
+
+ if (isRemoteControlEnabled(userInfo, enabled)) {
+ const currentTask = this.getCurrentCline()
+
+ if (currentTask && !currentTask.bridgeService) {
+ try {
+ currentTask.bridgeService = ExtensionBridgeService.getInstance()
+
+ if (currentTask.bridgeService) {
+ await currentTask.bridgeService.subscribeToTask(currentTask)
+ }
+ } catch (error) {
+ const message = `[ClineProvider#handleRemoteControlToggle] subscribeToTask failed - ${error instanceof Error ? error.message : String(error)}`
+ this.log(message)
+ console.error(message)
+ }
+ }
+ } else {
+ for (const task of this.clineStack) {
+ if (task.bridgeService) {
+ try {
+ await task.bridgeService.unsubscribeFromTask(task.taskId)
+ task.bridgeService = null
+ } catch (error) {
+ const message = `[ClineProvider#handleRemoteControlToggle] unsubscribeFromTask failed - ${error instanceof Error ? error.message : String(error)}`
+ this.log(message)
+ console.error(message)
+ }
+ }
+ }
+
+ ExtensionBridgeService.resetInstance()
+ }
+ }
+
/**
* Returns properties to be included in every telemetry event
* This method is called by the telemetry service to get context information
diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts
index eeae44451d..29df1e4a2e 100644
--- a/src/core/webview/__tests__/ClineProvider.spec.ts
+++ b/src/core/webview/__tests__/ClineProvider.spec.ts
@@ -4,8 +4,9 @@ import Anthropic from "@anthropic-ai/sdk"
import * as vscode from "vscode"
import axios from "axios"
-import { type ProviderSettingsEntry, type ClineMessage, ORGANIZATION_ALLOW_ALL } from "@roo-code/types"
+import { type ProviderSettingsEntry, type ClineMessage } from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
+import { ORGANIZATION_ALLOW_ALL } from "@roo-code/cloud"
import { ExtensionMessage, ExtensionState } from "../../../shared/ExtensionMessage"
import { defaultModeSlug } from "../../../shared/modes"
@@ -17,11 +18,9 @@ import { safeWriteJson } from "../../../utils/safeWriteJson"
import { ClineProvider } from "../ClineProvider"
-// Mock setup must come before imports
+// Mock setup must come before imports.
vi.mock("../../prompts/sections/custom-instructions")
-vi.mock("vscode")
-
vi.mock("p-wait-for", () => ({
__esModule: true,
default: vi.fn().mockResolvedValue(undefined),
@@ -95,7 +94,7 @@ vi.mock("../../../services/browser/browserDiscovery", () => ({
testBrowserConnection: vi.fn(),
}))
-// Remove duplicate mock - it's already defined below
+// Remove duplicate mock - it's already defined below.
const mockAddCustomInstructions = vi.fn().mockResolvedValue("Combined instructions")
@@ -228,7 +227,6 @@ vi.mock("../../../integrations/misc/extract-text", () => ({
}),
}))
-// Mock getModels for router model tests
vi.mock("../../../api/providers/fetchers/modelCache", () => ({
getModels: vi.fn().mockResolvedValue({}),
flushModels: vi.fn(),
@@ -324,6 +322,10 @@ vi.mock("@roo-code/cloud", () => ({
},
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
+ ORGANIZATION_ALLOW_ALL: {
+ allowAll: true,
+ providers: {},
+ },
}))
afterAll(() => {
diff --git a/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts b/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts
index e55e0910ab..99285bb68e 100644
--- a/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts
+++ b/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts
@@ -7,7 +7,6 @@ import { ContextProxy } from "../../config/ContextProxy"
import { Task } from "../../task/Task"
import type { HistoryItem, ProviderName } from "@roo-code/types"
-// Mock setup
vi.mock("vscode", () => ({
ExtensionContext: vi.fn(),
OutputChannel: vi.fn(),
@@ -54,7 +53,8 @@ vi.mock("vscode", () => ({
},
version: "1.85.0",
}))
-// Create a counter for unique task IDs
+
+// Create a counter for unique task IDs.
let taskIdCounter = 0
vi.mock("../../task/Task", () => ({
@@ -75,8 +75,11 @@ vi.mock("../../task/Task", () => ({
parentTask: options.parentTask,
})),
}))
+
vi.mock("../../prompts/sections/custom-instructions")
+
vi.mock("../../../utils/safeWriteJson")
+
vi.mock("../../../api", () => ({
buildApiHandler: vi.fn().mockReturnValue({
getModel: vi.fn().mockReturnValue({
@@ -85,12 +88,14 @@ vi.mock("../../../api", () => ({
}),
}),
}))
+
vi.mock("../../../integrations/workspace/WorkspaceTracker", () => ({
default: vi.fn().mockImplementation(() => ({
initializeFilePaths: vi.fn(),
dispose: vi.fn(),
})),
}))
+
vi.mock("../../diff/strategies/multi-search-replace", () => ({
MultiSearchReplaceDiffStrategy: vi.fn().mockImplementation(() => ({
getToolDescription: () => "test",
@@ -98,6 +103,7 @@ vi.mock("../../diff/strategies/multi-search-replace", () => ({
applyDiff: vi.fn(),
})),
}))
+
vi.mock("@roo-code/cloud", () => ({
CloudService: {
hasInstance: vi.fn().mockReturnValue(true),
@@ -108,7 +114,12 @@ vi.mock("@roo-code/cloud", () => ({
},
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
+ ORGANIZATION_ALLOW_ALL: {
+ allowAll: true,
+ providers: {},
+ },
}))
+
vi.mock("../../../shared/modes", () => ({
modes: [
{
@@ -132,20 +143,25 @@ vi.mock("../../../shared/modes", () => ({
}),
defaultModeSlug: "code",
}))
+
vi.mock("../../prompts/system", () => ({
SYSTEM_PROMPT: vi.fn().mockResolvedValue("mocked system prompt"),
codeMode: "code",
}))
+
vi.mock("../../../api/providers/fetchers/modelCache", () => ({
getModels: vi.fn().mockResolvedValue({}),
flushModels: vi.fn(),
}))
+
vi.mock("../../../integrations/misc/extract-text", () => ({
extractTextFromFile: vi.fn().mockResolvedValue("Mock file content"),
}))
+
vi.mock("p-wait-for", () => ({
default: vi.fn().mockImplementation(async () => Promise.resolve()),
}))
+
vi.mock("fs/promises", () => ({
mkdir: vi.fn().mockResolvedValue(undefined),
writeFile: vi.fn().mockResolvedValue(undefined),
@@ -153,6 +169,7 @@ vi.mock("fs/promises", () => ({
unlink: vi.fn().mockResolvedValue(undefined),
rmdir: vi.fn().mockResolvedValue(undefined),
}))
+
vi.mock("@roo-code/telemetry", () => ({
TelemetryService: {
hasInstance: vi.fn().mockReturnValue(true),
diff --git a/src/core/webview/__tests__/webviewMessageHandler.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.spec.ts
index 8e61f3f0d9..7ba7128bb9 100644
--- a/src/core/webview/__tests__/webviewMessageHandler.spec.ts
+++ b/src/core/webview/__tests__/webviewMessageHandler.spec.ts
@@ -35,7 +35,6 @@ const mockClineProvider = {
getCurrentCline: vi.fn(),
getTaskWithId: vi.fn(),
initClineWithHistoryItem: vi.fn(),
- getMcpHub: vi.fn(),
} as unknown as ClineProvider
import { t } from "../../../i18n"
@@ -589,138 +588,43 @@ describe("webviewMessageHandler - mcpEnabled", () => {
handleMcpEnabledChange: vi.fn().mockResolvedValue(undefined),
}
- // Mock the getMcpHub method to return our mock McpHub
- mockClineProvider.getMcpHub = vi.fn().mockReturnValue(mockMcpHub)
-
- // Reset the contextProxy getValue mock
- vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(undefined)
+ // Ensure provider exposes getMcpHub and returns our mock
+ ;(mockClineProvider as any).getMcpHub = vi.fn().mockReturnValue(mockMcpHub)
})
- it("should not refresh MCP servers when value does not change (true to true)", async () => {
- // Setup: mcpEnabled is already true
- vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
-
- // Act: Send mcpEnabled message with same value
+ it("delegates enable=true to McpHub and posts updated state", async () => {
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: true,
})
- // Assert: handleMcpEnabledChange should not be called
- expect(mockMcpHub.handleMcpEnabledChange).not.toHaveBeenCalled()
- expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
- expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
- })
-
- it("should not refresh MCP servers when value does not change (false to false)", async () => {
- // Setup: mcpEnabled is already false
- vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)
-
- // Act: Send mcpEnabled message with same value
- await webviewMessageHandler(mockClineProvider, {
- type: "mcpEnabled",
- bool: false,
- })
-
- // Assert: handleMcpEnabledChange should not be called
- expect(mockMcpHub.handleMcpEnabledChange).not.toHaveBeenCalled()
- expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
- expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
- })
-
- it("should refresh MCP servers when value changes from true to false", async () => {
- // Setup: mcpEnabled is true
- vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
-
- // Act: Send mcpEnabled message with false
- await webviewMessageHandler(mockClineProvider, {
- type: "mcpEnabled",
- bool: false,
- })
-
- // Assert: handleMcpEnabledChange should be called
- expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledWith(false)
- expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
- expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
- })
-
- it("should refresh MCP servers when value changes from false to true", async () => {
- // Setup: mcpEnabled is false
- vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)
-
- // Act: Send mcpEnabled message with true
- await webviewMessageHandler(mockClineProvider, {
- type: "mcpEnabled",
- bool: true,
- })
-
- // Assert: handleMcpEnabledChange should be called
+ expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
+ expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledTimes(1)
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledWith(true)
- expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
- expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
+ expect(mockClineProvider.postStateToWebview).toHaveBeenCalledTimes(1)
})
- it("should handle undefined values with defaults correctly", async () => {
- // Setup: mcpEnabled is undefined (defaults to true)
- vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(undefined)
-
- // Act: Send mcpEnabled message with undefined (defaults to true)
- await webviewMessageHandler(mockClineProvider, {
- type: "mcpEnabled",
- bool: undefined,
- })
-
- // Assert: Should use default value (true) and not trigger refresh since both are true
- expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
- expect(mockMcpHub.handleMcpEnabledChange).not.toHaveBeenCalled()
- expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
- })
-
- it("should handle when mcpEnabled changes from undefined to false", async () => {
- // Setup: mcpEnabled is undefined (defaults to true)
- vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(undefined)
-
- // Act: Send mcpEnabled message with false
+ it("delegates enable=false to McpHub and posts updated state", async () => {
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: false,
})
- // Assert: Should trigger refresh since undefined defaults to true and we're changing to false
- expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
+ expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
+ expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledTimes(1)
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledWith(false)
- expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
+ expect(mockClineProvider.postStateToWebview).toHaveBeenCalledTimes(1)
})
- it("should not call handleMcpEnabledChange when McpHub is not available", async () => {
- // Setup: No McpHub instance available
- mockClineProvider.getMcpHub = vi.fn().mockReturnValue(null)
- vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
+ it("handles missing McpHub instance gracefully and still posts state", async () => {
+ ;(mockClineProvider as any).getMcpHub = vi.fn().mockReturnValue(undefined)
- // Act: Send mcpEnabled message with false
- await webviewMessageHandler(mockClineProvider, {
- type: "mcpEnabled",
- bool: false,
- })
-
- // Assert: State should be updated but handleMcpEnabledChange should not be called
- expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
- expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
- // No error should be thrown
- })
-
- it("should always update state even when value doesn't change", async () => {
- // Setup: mcpEnabled is true
- vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
-
- // Act: Send mcpEnabled message with same value
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: true,
})
- // Assert: State should still be updated to ensure consistency
- expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
- expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
+ expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
+ expect(mockClineProvider.postStateToWebview).toHaveBeenCalledTimes(1)
})
})
diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts
index e2c6d6a475..7e25ae14dc 100644
--- a/src/core/webview/webviewMessageHandler.ts
+++ b/src/core/webview/webviewMessageHandler.ts
@@ -548,6 +548,15 @@ export const webviewMessageHandler = async (
{ key: "unbound", options: { provider: "unbound", apiKey: apiConfiguration.unboundApiKey } },
]
+ // Add IO Intelligence if API key is provided
+ const ioIntelligenceApiKey = apiConfiguration.ioIntelligenceApiKey
+ if (ioIntelligenceApiKey) {
+ modelFetchPromises.push({
+ key: "io-intelligence",
+ options: { provider: "io-intelligence", apiKey: ioIntelligenceApiKey },
+ })
+ }
+
// Don't fetch Ollama and LM Studio models by default anymore
// They have their own specific handlers: requestOllamaModels and requestLmStudioModels
@@ -900,19 +909,12 @@ export const webviewMessageHandler = async (
}
case "mcpEnabled":
const mcpEnabled = message.bool ?? true
- const currentMcpEnabled = getGlobalState("mcpEnabled") ?? true
-
- // Always update the state to ensure consistency
await updateGlobalState("mcpEnabled", mcpEnabled)
- // Only refresh MCP connections if the value actually changed
- // This prevents expensive MCP server refresh operations when saving unrelated settings
- if (currentMcpEnabled !== mcpEnabled) {
- // Delegate MCP enable/disable logic to McpHub
- const mcpHubInstance = provider.getMcpHub()
- if (mcpHubInstance) {
- await mcpHubInstance.handleMcpEnabledChange(mcpEnabled)
- }
+ // Delegate MCP enable/disable logic to McpHub
+ const mcpHubInstance = provider.getMcpHub()
+ if (mcpHubInstance) {
+ await mcpHubInstance.handleMcpEnabledChange(mcpEnabled)
}
await provider.postStateToWebview()
@@ -921,6 +923,11 @@ export const webviewMessageHandler = async (
await updateGlobalState("enableMcpServerCreation", message.bool ?? true)
await provider.postStateToWebview()
break
+ case "remoteControlEnabled":
+ await updateGlobalState("remoteControlEnabled", message.bool ?? false)
+ await provider.handleRemoteControlToggle(message.bool ?? false)
+ await provider.postStateToWebview()
+ break
case "refreshAllMcpServers": {
const mcpHub = provider.getMcpHub()
if (mcpHub) {
diff --git a/src/extension.ts b/src/extension.ts
index 2d95902295..6cb6ea4b07 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -12,7 +12,7 @@ try {
console.warn("Failed to load environment variables:", e)
}
-import { CloudService } from "@roo-code/cloud"
+import { CloudService, ExtensionBridgeService } from "@roo-code/cloud"
import { TelemetryService, PostHogTelemetryClient } from "@roo-code/telemetry"
import "./utils/path" // Necessary to have access to String.prototype.toPosix.
@@ -29,6 +29,7 @@ import { CodeIndexManager } from "./services/code-index/manager"
import { MdmService } from "./services/mdm/MdmService"
import { migrateSettings } from "./utils/migrateSettings"
import { autoImportSettings } from "./utils/autoImportSettings"
+import { isRemoteControlEnabled } from "./utils/remoteControl"
import { API } from "./extension/api"
import {
@@ -71,37 +72,13 @@ export async function activate(context: vscode.ExtensionContext) {
console.warn("Failed to register PostHogTelemetryClient:", error)
}
- // Create logger for cloud services
+ // Create logger for cloud services.
const cloudLogger = createDualLogger(createOutputChannelLogger(outputChannel))
- // Initialize Roo Code Cloud service.
- const cloudService = await CloudService.createInstance(context, cloudLogger)
-
- try {
- if (cloudService.telemetryClient) {
- TelemetryService.instance.register(cloudService.telemetryClient)
- }
- } catch (error) {
- outputChannel.appendLine(
- `[CloudService] Failed to register TelemetryClient: ${error instanceof Error ? error.message : String(error)}`,
- )
- }
-
- const postStateListener = () => {
- ClineProvider.getVisibleInstance()?.postStateToWebview()
- }
-
- cloudService.on("auth-state-changed", postStateListener)
- cloudService.on("user-info", postStateListener)
- cloudService.on("settings-updated", postStateListener)
-
- // Add to subscriptions for proper cleanup on deactivate
- context.subscriptions.push(cloudService)
-
// Initialize MDM service
const mdmService = await MdmService.createInstance(cloudLogger)
- // Initialize i18n for internationalization support
+ // Initialize i18n for internationalization support.
initializeI18n(context.globalState.get("language") ?? formatLanguage(vscode.env.language))
// Initialize terminal shell execution handlers.
@@ -136,6 +113,45 @@ export async function activate(context: vscode.ExtensionContext) {
}
}
+ // Initialize Roo Code Cloud service.
+ const cloudService = await CloudService.createInstance(context, cloudLogger)
+
+ try {
+ if (cloudService.telemetryClient) {
+ TelemetryService.instance.register(cloudService.telemetryClient)
+ }
+ } catch (error) {
+ outputChannel.appendLine(
+ `[CloudService] Failed to register TelemetryClient: ${error instanceof Error ? error.message : String(error)}`,
+ )
+ }
+
+ const postStateListener = () => ClineProvider.getVisibleInstance()?.postStateToWebview()
+
+ cloudService.on("auth-state-changed", postStateListener)
+ cloudService.on("settings-updated", postStateListener)
+
+ cloudService.on("user-info", async ({ userInfo }) => {
+ postStateListener()
+
+ const bridgeConfig = await cloudService.cloudAPI?.bridgeConfig().catch(() => undefined)
+
+ if (!bridgeConfig) {
+ outputChannel.appendLine("[CloudService] Failed to get bridge config")
+ return
+ }
+
+ ExtensionBridgeService.handleRemoteControlState(
+ userInfo,
+ contextProxy.getValue("remoteControlEnabled"),
+ { ...bridgeConfig, provider, sessionId: vscode.env.sessionId },
+ (message: string) => outputChannel.appendLine(message),
+ )
+ })
+
+ // Add to subscriptions for proper cleanup on deactivate.
+ context.subscriptions.push(cloudService)
+
const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy, mdmService)
TelemetryService.instance.setProvider(provider)
@@ -145,7 +161,7 @@ export async function activate(context: vscode.ExtensionContext) {
}),
)
- // Auto-import configuration if specified in settings
+ // Auto-import configuration if specified in settings.
try {
await autoImportSettings(outputChannel, {
providerSettingsManager: provider.providerSettingsManager,
@@ -263,6 +279,13 @@ export async function activate(context: vscode.ExtensionContext) {
// This method is called when your extension is deactivated.
export async function deactivate() {
outputChannel.appendLine(`${Package.name} extension deactivated`)
+
+ const bridgeService = ExtensionBridgeService.getInstance()
+
+ if (bridgeService) {
+ await bridgeService.disconnect()
+ }
+
await McpServerManager.cleanup(extensionContext)
TelemetryService.instance.shutdown()
TerminalRegistry.cleanup()
diff --git a/src/package.json b/src/package.json
index 499cd403ce..084e42e08e 100644
--- a/src/package.json
+++ b/src/package.json
@@ -3,7 +3,7 @@
"displayName": "%extension.displayName%",
"description": "%extension.description%",
"publisher": "RooVeterinaryInc",
- "version": "3.25.10",
+ "version": "3.25.15",
"icon": "assets/icons/icon.png",
"galleryBanner": {
"color": "#617A91",
@@ -102,7 +102,7 @@
},
{
"command": "roo-cline.accountButtonClicked",
- "title": "Account",
+ "title": "%command.account.title%",
"icon": "$(account)"
},
{
@@ -391,6 +391,13 @@
"type": "boolean",
"default": true,
"description": "%settings.useAgentRules.description%"
+ },
+ "roo-cline.apiRequestTimeout": {
+ "type": "number",
+ "default": 600,
+ "minimum": 0,
+ "maximum": 3600,
+ "description": "%settings.apiRequestTimeout.description%"
}
}
}
@@ -420,7 +427,7 @@
"@mistralai/mistralai": "^1.3.6",
"@modelcontextprotocol/sdk": "^1.9.0",
"@qdrant/js-client-rest": "^1.14.0",
- "@roo-code/cloud": "^0.5.0",
+ "@roo-code/cloud": "^0.14.0",
"@roo-code/ipc": "workspace:^",
"@roo-code/telemetry": "workspace:^",
"@roo-code/types": "workspace:^",
@@ -467,6 +474,7 @@
"say": "^0.16.0",
"serialize-error": "^12.0.0",
"simple-git": "^3.27.0",
+ "socket.io-client": "^4.8.1",
"sound-play": "^1.1.0",
"stream-json": "^1.8.0",
"string-similarity": "^4.0.4",
diff --git a/src/package.nls.ca.json b/src/package.nls.ca.json
index 4340b80c2b..1e2f1eedd6 100644
--- a/src/package.nls.ca.json
+++ b/src/package.nls.ca.json
@@ -23,6 +23,7 @@
"command.history.title": "Historial",
"command.marketplace.title": "Mercat",
"command.openInEditor.title": "Obrir a l'Editor",
+ "command.account.title": "Compte",
"command.settings.title": "Configuració",
"command.documentation.title": "Documentació",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Ruta d'emmagatzematge personalitzada. Deixeu-la buida per utilitzar la ubicació predeterminada. Admet rutes absolutes (p. ex. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Habilitar correccions ràpides de Roo Code.",
"settings.autoImportSettingsPath.description": "Ruta a un fitxer de configuració de RooCode per importar automàticament en iniciar l'extensió. Admet rutes absolutes i rutes relatives al directori d'inici (per exemple, '~/Documents/roo-code-settings.json'). Deixeu-ho en blanc per desactivar la importació automàtica.",
- "settings.useAgentRules.description": "Activa la càrrega de fitxers AGENTS.md per a regles específiques de l'agent (vegeu https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Activa la càrrega de fitxers AGENTS.md per a regles específiques de l'agent (vegeu https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Temps màxim en segons per esperar les respostes de l'API (0 = sense temps d'espera, 1-3600s, per defecte: 600s). Es recomanen valors més alts per a proveïdors locals com LM Studio i Ollama que poden necessitar més temps de processament."
}
diff --git a/src/package.nls.de.json b/src/package.nls.de.json
index c0d84fb0bd..b5f099ff66 100644
--- a/src/package.nls.de.json
+++ b/src/package.nls.de.json
@@ -23,6 +23,7 @@
"command.history.title": "Verlauf",
"command.marketplace.title": "Marktplatz",
"command.openInEditor.title": "Im Editor Öffnen",
+ "command.account.title": "Konto",
"command.settings.title": "Einstellungen",
"command.documentation.title": "Dokumentation",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Benutzerdefinierter Speicherpfad. Leer lassen, um den Standardspeicherort zu verwenden. Unterstützt absolute Pfade (z.B. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Roo Code Schnelle Problembehebung aktivieren.",
"settings.autoImportSettingsPath.description": "Pfad zu einer RooCode-Konfigurationsdatei, die beim Start der Erweiterung automatisch importiert wird. Unterstützt absolute Pfade und Pfade relativ zum Home-Verzeichnis (z.B. '~/Documents/roo-code-settings.json'). Leer lassen, um den automatischen Import zu deaktivieren.",
- "settings.useAgentRules.description": "Aktiviert das Laden von AGENTS.md-Dateien für agentenspezifische Regeln (siehe https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Aktiviert das Laden von AGENTS.md-Dateien für agentenspezifische Regeln (siehe https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Maximale Wartezeit in Sekunden auf API-Antworten (0 = kein Timeout, 1-3600s, Standard: 600s). Höhere Werte werden für lokale Anbieter wie LM Studio und Ollama empfohlen, die möglicherweise mehr Verarbeitungszeit benötigen."
}
diff --git a/src/package.nls.es.json b/src/package.nls.es.json
index 37c1eda344..a2479cd770 100644
--- a/src/package.nls.es.json
+++ b/src/package.nls.es.json
@@ -23,6 +23,7 @@
"command.history.title": "Historial",
"command.marketplace.title": "Mercado",
"command.openInEditor.title": "Abrir en Editor",
+ "command.account.title": "Cuenta",
"command.settings.title": "Configuración",
"command.documentation.title": "Documentación",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Ruta de almacenamiento personalizada. Dejar vacío para usar la ubicación predeterminada. Admite rutas absolutas (ej. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Habilitar correcciones rápidas de Roo Code.",
"settings.autoImportSettingsPath.description": "Ruta a un archivo de configuración de RooCode para importar automáticamente al iniciar la extensión. Admite rutas absolutas y rutas relativas al directorio de inicio (por ejemplo, '~/Documents/roo-code-settings.json'). Dejar vacío para desactivar la importación automática.",
- "settings.useAgentRules.description": "Habilita la carga de archivos AGENTS.md para reglas específicas del agente (ver https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Habilita la carga de archivos AGENTS.md para reglas específicas del agente (ver https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Tiempo máximo en segundos de espera para las respuestas de la API (0 = sin tiempo de espera, 1-3600s, por defecto: 600s). Se recomiendan valores más altos para proveedores locales como LM Studio y Ollama que puedan necesitar más tiempo de procesamiento."
}
diff --git a/src/package.nls.fr.json b/src/package.nls.fr.json
index e040bd201b..03052aaf75 100644
--- a/src/package.nls.fr.json
+++ b/src/package.nls.fr.json
@@ -23,6 +23,7 @@
"command.history.title": "Historique",
"command.marketplace.title": "Marché",
"command.openInEditor.title": "Ouvrir dans l'Éditeur",
+ "command.account.title": "Compte",
"command.settings.title": "Paramètres",
"command.documentation.title": "Documentation",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Chemin de stockage personnalisé. Laisser vide pour utiliser l'emplacement par défaut. Prend en charge les chemins absolus (ex: 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Activer les correctifs rapides de Roo Code.",
"settings.autoImportSettingsPath.description": "Chemin d'accès à un fichier de configuration RooCode à importer automatiquement au démarrage de l'extension. Prend en charge les chemins absolus et les chemins relatifs au répertoire de base (par exemple, '~/Documents/roo-code-settings.json'). Laisser vide pour désactiver l'importation automatique.",
- "settings.useAgentRules.description": "Activer le chargement des fichiers AGENTS.md pour les règles spécifiques à l'agent (voir https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Activer le chargement des fichiers AGENTS.md pour les règles spécifiques à l'agent (voir https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Temps maximum en secondes d'attente pour les réponses de l'API (0 = pas de timeout, 1-3600s, par défaut : 600s). Des valeurs plus élevées sont recommandées pour les fournisseurs locaux comme LM Studio et Ollama qui peuvent nécessiter plus de temps de traitement."
}
diff --git a/src/package.nls.hi.json b/src/package.nls.hi.json
index a67d8f30ed..24f8ed402d 100644
--- a/src/package.nls.hi.json
+++ b/src/package.nls.hi.json
@@ -23,6 +23,7 @@
"command.history.title": "इतिहास",
"command.marketplace.title": "मार्केटप्लेस",
"command.openInEditor.title": "एडिटर में खोलें",
+ "command.account.title": "खाता",
"command.settings.title": "सेटिंग्स",
"command.documentation.title": "दस्तावेज़ीकरण",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "कस्टम स्टोरेज पाथ। डिफ़ॉल्ट स्थान का उपयोग करने के लिए खाली छोड़ें। पूर्ण पथ का समर्थन करता है (उदा. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Roo Code त्वरित सुधार सक्षम करें",
"settings.autoImportSettingsPath.description": "RooCode कॉन्फ़िगरेशन फ़ाइल का पथ जिसे एक्सटेंशन स्टार्टअप पर स्वचालित रूप से आयात किया जाएगा। होम डायरेक्टरी के सापेक्ष पूर्ण पथ और पथों का समर्थन करता है (उदाहरण के लिए '~/Documents/roo-code-settings.json')। ऑटो-इंपोर्ट को अक्षम करने के लिए खाली छोड़ दें।",
- "settings.useAgentRules.description": "एजेंट-विशिष्ट नियमों के लिए AGENTS.md फ़ाइलों को लोड करना सक्षम करें (देखें https://agent-rules.org/)"
+ "settings.useAgentRules.description": "एजेंट-विशिष्ट नियमों के लिए AGENTS.md फ़ाइलों को लोड करना सक्षम करें (देखें https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "एपीआई प्रतिक्रियाओं की प्रतीक्षा करने के लिए सेकंड में अधिकतम समय (0 = कोई टाइमआउट नहीं, 1-3600s, डिफ़ॉल्ट: 600s)। एलएम स्टूडियो और ओलामा जैसे स्थानीय प्रदाताओं के लिए उच्च मानों की सिफारिश की जाती है जिन्हें अधिक प्रसंस्करण समय की आवश्यकता हो सकती है।"
}
diff --git a/src/package.nls.id.json b/src/package.nls.id.json
index 988c41bf36..98e979ee07 100644
--- a/src/package.nls.id.json
+++ b/src/package.nls.id.json
@@ -11,6 +11,7 @@
"command.history.title": "Riwayat",
"command.marketplace.title": "Marketplace",
"command.openInEditor.title": "Buka di Editor",
+ "command.account.title": "Akun",
"command.settings.title": "Pengaturan",
"command.documentation.title": "Dokumentasi",
"command.openInNewTab.title": "Buka di Tab Baru",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Path penyimpanan kustom. Biarkan kosong untuk menggunakan lokasi default. Mendukung path absolut (misalnya 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Aktifkan perbaikan cepat Roo Code.",
"settings.autoImportSettingsPath.description": "Path ke file konfigurasi RooCode untuk diimpor secara otomatis saat ekstensi dimulai. Mendukung path absolut dan path relatif terhadap direktori home (misalnya '~/Documents/roo-code-settings.json'). Biarkan kosong untuk menonaktifkan impor otomatis.",
- "settings.useAgentRules.description": "Aktifkan pemuatan file AGENTS.md untuk aturan khusus agen (lihat https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Aktifkan pemuatan file AGENTS.md untuk aturan khusus agen (lihat https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Waktu maksimum dalam detik untuk menunggu respons API (0 = tidak ada batas waktu, 1-3600s, default: 600s). Nilai yang lebih tinggi disarankan untuk penyedia lokal seperti LM Studio dan Ollama yang mungkin memerlukan lebih banyak waktu pemrosesan."
}
diff --git a/src/package.nls.it.json b/src/package.nls.it.json
index 726e28593b..6f8bb8fbce 100644
--- a/src/package.nls.it.json
+++ b/src/package.nls.it.json
@@ -23,6 +23,7 @@
"command.history.title": "Cronologia",
"command.marketplace.title": "Marketplace",
"command.openInEditor.title": "Apri nell'Editor",
+ "command.account.title": "Account",
"command.settings.title": "Impostazioni",
"command.documentation.title": "Documentazione",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Percorso di archiviazione personalizzato. Lasciare vuoto per utilizzare la posizione predefinita. Supporta percorsi assoluti (es. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Abilita correzioni rapide di Roo Code.",
"settings.autoImportSettingsPath.description": "Percorso di un file di configurazione di RooCode da importare automaticamente all'avvio dell'estensione. Supporta percorsi assoluti e percorsi relativi alla directory home (ad es. '~/Documents/roo-code-settings.json'). Lasciare vuoto per disabilitare l'importazione automatica.",
- "settings.useAgentRules.description": "Abilita il caricamento dei file AGENTS.md per regole specifiche dell'agente (vedi https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Abilita il caricamento dei file AGENTS.md per regole specifiche dell'agente (vedi https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Tempo massimo in secondi di attesa per le risposte API (0 = nessun timeout, 1-3600s, predefinito: 600s). Valori più alti sono consigliati per provider locali come LM Studio e Ollama che potrebbero richiedere più tempo di elaborazione."
}
diff --git a/src/package.nls.ja.json b/src/package.nls.ja.json
index fa1e6c202d..c26c46905d 100644
--- a/src/package.nls.ja.json
+++ b/src/package.nls.ja.json
@@ -11,6 +11,7 @@
"command.history.title": "履歴",
"command.marketplace.title": "マーケットプレイス",
"command.openInEditor.title": "エディタで開く",
+ "command.account.title": "アカウント",
"command.settings.title": "設定",
"command.documentation.title": "ドキュメント",
"command.openInNewTab.title": "新しいタブで開く",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "カスタムストレージパス。デフォルトの場所を使用する場合は空のままにします。絶対パスをサポートします(例:'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Roo Codeのクイック修正を有効にする。",
"settings.autoImportSettingsPath.description": "拡張機能の起動時に自動的にインポートするRooCode設定ファイルへのパス。絶対パスとホームディレクトリからの相対パスをサポートします(例:'~/Documents/roo-code-settings.json')。自動インポートを無効にするには、空のままにします。",
- "settings.useAgentRules.description": "エージェント固有のルールのためにAGENTS.mdファイルの読み込みを有効にします(参照:https://agent-rules.org/)"
+ "settings.useAgentRules.description": "エージェント固有のルールのためにAGENTS.mdファイルの読み込みを有効にします(参照:https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "API応答を待機する最大時間(秒)(0 = タイムアウトなし、1-3600秒、デフォルト: 600秒)。LM StudioやOllamaのような、より多くの処理時間を必要とする可能性のあるローカルプロバイダーには、より高い値が推奨されます。"
}
diff --git a/src/package.nls.json b/src/package.nls.json
index 36ef72a823..f1c132be65 100644
--- a/src/package.nls.json
+++ b/src/package.nls.json
@@ -11,6 +11,7 @@
"command.history.title": "History",
"command.marketplace.title": "Marketplace",
"command.openInEditor.title": "Open in Editor",
+ "command.account.title": "Account",
"command.settings.title": "Settings",
"command.documentation.title": "Documentation",
"command.openInNewTab.title": "Open In New Tab",
@@ -37,5 +38,6 @@
"settings.customStoragePath.description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Enable Roo Code quick fixes",
"settings.autoImportSettingsPath.description": "Path to a RooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/roo-code-settings.json'). Leave empty to disable auto-import.",
- "settings.useAgentRules.description": "Enable loading of AGENTS.md files for agent-specific rules (see https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Enable loading of AGENTS.md files for agent-specific rules (see https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Maximum time in seconds to wait for API responses (0 = no timeout, 1-3600s, default: 600s). Higher values are recommended for local providers like LM Studio and Ollama that may need more processing time."
}
diff --git a/src/package.nls.ko.json b/src/package.nls.ko.json
index c55566f043..b4d5dd4c14 100644
--- a/src/package.nls.ko.json
+++ b/src/package.nls.ko.json
@@ -23,6 +23,7 @@
"command.history.title": "기록",
"command.marketplace.title": "마켓플레이스",
"command.openInEditor.title": "에디터에서 열기",
+ "command.account.title": "계정",
"command.settings.title": "설정",
"command.documentation.title": "문서",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "사용자 지정 저장소 경로. 기본 위치를 사용하려면 비워두세요. 절대 경로를 지원합니다 (예: 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Roo Code 빠른 수정 사용 설정",
"settings.autoImportSettingsPath.description": "확장 프로그램 시작 시 자동으로 가져올 RooCode 구성 파일의 경로입니다. 절대 경로 및 홈 디렉토리에 대한 상대 경로를 지원합니다(예: '~/Documents/roo-code-settings.json'). 자동 가져오기를 비활성화하려면 비워 둡니다.",
- "settings.useAgentRules.description": "에이전트별 규칙에 대한 AGENTS.md 파일 로드를 활성화합니다 (참조: https://agent-rules.org/)"
+ "settings.useAgentRules.description": "에이전트별 규칙에 대한 AGENTS.md 파일 로드를 활성화합니다 (참조: https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "API 응답을 기다리는 최대 시간(초) (0 = 시간 초과 없음, 1-3600초, 기본값: 600초). 더 많은 처리 시간이 필요할 수 있는 LM Studio 및 Ollama와 같은 로컬 공급자에게는 더 높은 값을 사용하는 것이 좋습니다."
}
diff --git a/src/package.nls.nl.json b/src/package.nls.nl.json
index edcc5f7d12..22d9102f18 100644
--- a/src/package.nls.nl.json
+++ b/src/package.nls.nl.json
@@ -11,6 +11,7 @@
"command.history.title": "Geschiedenis",
"command.marketplace.title": "Marktplaats",
"command.openInEditor.title": "Openen in Editor",
+ "command.account.title": "Account",
"command.settings.title": "Instellingen",
"command.documentation.title": "Documentatie",
"command.openInNewTab.title": "Openen in Nieuw Tabblad",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Aangepast opslagpad. Laat leeg om de standaardlocatie te gebruiken. Ondersteunt absolute paden (bijv. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Snelle correcties van Roo Code inschakelen.",
"settings.autoImportSettingsPath.description": "Pad naar een RooCode-configuratiebestand om automatisch te importeren bij het opstarten van de extensie. Ondersteunt absolute paden en paden ten opzichte van de thuismap (bijv. '~/Documents/roo-code-settings.json'). Laat leeg om automatisch importeren uit te schakelen.",
- "settings.useAgentRules.description": "Laden van AGENTS.md-bestanden voor agentspecifieke regels inschakelen (zie https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Laden van AGENTS.md-bestanden voor agentspecifieke regels inschakelen (zie https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Maximale tijd in seconden om te wachten op API-reacties (0 = geen time-out, 1-3600s, standaard: 600s). Hogere waarden worden aanbevolen voor lokale providers zoals LM Studio en Ollama die mogelijk meer verwerkingstijd nodig hebben."
}
diff --git a/src/package.nls.pl.json b/src/package.nls.pl.json
index c7547ac4c9..cb23404330 100644
--- a/src/package.nls.pl.json
+++ b/src/package.nls.pl.json
@@ -23,6 +23,7 @@
"command.history.title": "Historia",
"command.marketplace.title": "Marketplace",
"command.openInEditor.title": "Otwórz w Edytorze",
+ "command.account.title": "Konto",
"command.settings.title": "Ustawienia",
"command.documentation.title": "Dokumentacja",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Niestandardowa ścieżka przechowywania. Pozostaw puste, aby użyć domyślnej lokalizacji. Obsługuje ścieżki bezwzględne (np. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Włącz szybkie poprawki Roo Code.",
"settings.autoImportSettingsPath.description": "Ścieżka do pliku konfiguracyjnego RooCode, który ma być automatycznie importowany podczas uruchamiania rozszerzenia. Obsługuje ścieżki bezwzględne i ścieżki względne do katalogu domowego (np. '~/Documents/roo-code-settings.json'). Pozostaw puste, aby wyłączyć automatyczne importowanie.",
- "settings.useAgentRules.description": "Włącz wczytywanie plików AGENTS.md dla reguł specyficznych dla agenta (zobacz https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Włącz wczytywanie plików AGENTS.md dla reguł specyficznych dla agenta (zobacz https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Maksymalny czas w sekundach oczekiwania na odpowiedzi API (0 = brak limitu czasu, 1-3600s, domyślnie: 600s). Wyższe wartości są zalecane dla lokalnych dostawców, takich jak LM Studio i Ollama, którzy mogą potrzebować więcej czasu na przetwarzanie."
}
diff --git a/src/package.nls.pt-BR.json b/src/package.nls.pt-BR.json
index 0b7f6c0d47..e3b432e1d5 100644
--- a/src/package.nls.pt-BR.json
+++ b/src/package.nls.pt-BR.json
@@ -23,6 +23,7 @@
"command.history.title": "Histórico",
"command.marketplace.title": "Marketplace",
"command.openInEditor.title": "Abrir no Editor",
+ "command.account.title": "Conta",
"command.settings.title": "Configurações",
"command.documentation.title": "Documentação",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Caminho de armazenamento personalizado. Deixe vazio para usar o local padrão. Suporta caminhos absolutos (ex: 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Habilitar correções rápidas do Roo Code.",
"settings.autoImportSettingsPath.description": "Caminho para um arquivo de configuração do RooCode para importar automaticamente na inicialização da extensão. Suporta caminhos absolutos e caminhos relativos ao diretório inicial (por exemplo, '~/Documents/roo-code-settings.json'). Deixe em branco para desativar a importação automática.",
- "settings.useAgentRules.description": "Habilita o carregamento de arquivos AGENTS.md para regras específicas do agente (consulte https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Habilita o carregamento de arquivos AGENTS.md para regras específicas do agente (consulte https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Tempo máximo em segundos de espera pelas respostas da API (0 = sem tempo limite, 1-3600s, padrão: 600s). Valores mais altos são recomendados para provedores locais como LM Studio e Ollama que podem precisar de mais tempo de processamento."
}
diff --git a/src/package.nls.ru.json b/src/package.nls.ru.json
index 380337249d..91da74c6f1 100644
--- a/src/package.nls.ru.json
+++ b/src/package.nls.ru.json
@@ -11,6 +11,7 @@
"command.history.title": "История",
"command.marketplace.title": "Маркетплейс",
"command.openInEditor.title": "Открыть в редакторе",
+ "command.account.title": "Аккаунт",
"command.settings.title": "Настройки",
"command.documentation.title": "Документация",
"command.openInNewTab.title": "Открыть в новой вкладке",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Пользовательский путь хранения. Оставьте пустым для использования пути по умолчанию. Поддерживает абсолютные пути (например, 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Включить быстрые исправления Roo Code.",
"settings.autoImportSettingsPath.description": "Путь к файлу конфигурации RooCode для автоматического импорта при запуске расширения. Поддерживает абсолютные пути и пути относительно домашнего каталога (например, '~/Documents/roo-code-settings.json'). Оставьте пустым, чтобы отключить автоматический импорт.",
- "settings.useAgentRules.description": "Включить загрузку файлов AGENTS.md для специфичных для агента правил (см. https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Включить загрузку файлов AGENTS.md для специфичных для агента правил (см. https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Максимальное время в секундах для ожидания ответов API (0 = нет тайм-аута, 1-3600 с, по умолчанию: 600 с). Рекомендуются более высокие значения для локальных провайдеров, таких как LM Studio и Ollama, которым может потребоваться больше времени на обработку."
}
diff --git a/src/package.nls.tr.json b/src/package.nls.tr.json
index d5ba65da73..5337db6bb4 100644
--- a/src/package.nls.tr.json
+++ b/src/package.nls.tr.json
@@ -23,6 +23,7 @@
"command.history.title": "Geçmiş",
"command.marketplace.title": "Marketplace",
"command.openInEditor.title": "Düzenleyicide Aç",
+ "command.account.title": "Hesap",
"command.settings.title": "Ayarlar",
"command.documentation.title": "Dokümantasyon",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Özel depolama yolu. Varsayılan konumu kullanmak için boş bırakın. Mutlak yolları destekler (örn: 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Roo Code hızlı düzeltmeleri etkinleştir.",
"settings.autoImportSettingsPath.description": "Uzantı başlangıcında otomatik olarak içe aktarılacak bir RooCode yapılandırma dosyasının yolu. Mutlak yolları ve ana dizine göreli yolları destekler (ör. '~/Documents/roo-code-settings.json'). Otomatik içe aktarmayı devre dışı bırakmak için boş bırakın.",
- "settings.useAgentRules.description": "Aracıya özgü kurallar için AGENTS.md dosyalarının yüklenmesini etkinleştirin (bkz. https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Aracıya özgü kurallar için AGENTS.md dosyalarının yüklenmesini etkinleştirin (bkz. https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "API yanıtları için beklenecek maksimum süre (saniye cinsinden) (0 = zaman aşımı yok, 1-3600s, varsayılan: 600s). LM Studio ve Ollama gibi daha fazla işlem süresi gerektirebilecek yerel sağlayıcılar için daha yüksek değerler önerilir."
}
diff --git a/src/package.nls.vi.json b/src/package.nls.vi.json
index 503d1139b8..f8c79b748a 100644
--- a/src/package.nls.vi.json
+++ b/src/package.nls.vi.json
@@ -23,6 +23,7 @@
"command.history.title": "Lịch Sử",
"command.marketplace.title": "Marketplace",
"command.openInEditor.title": "Mở trong Trình Soạn Thảo",
+ "command.account.title": "Tài khoản",
"command.settings.title": "Cài Đặt",
"command.documentation.title": "Tài Liệu",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "Đường dẫn lưu trữ tùy chỉnh. Để trống để sử dụng vị trí mặc định. Hỗ trợ đường dẫn tuyệt đối (ví dụ: 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Bật sửa lỗi nhanh Roo Code.",
"settings.autoImportSettingsPath.description": "Đường dẫn đến tệp cấu hình RooCode để tự động nhập khi khởi động tiện ích mở rộng. Hỗ trợ đường dẫn tuyệt đối và đường dẫn tương đối đến thư mục chính (ví dụ: '~/Documents/roo-code-settings.json'). Để trống để tắt tính năng tự động nhập.",
- "settings.useAgentRules.description": "Bật tải tệp AGENTS.md cho các quy tắc dành riêng cho tác nhân (xem https://agent-rules.org/)"
+ "settings.useAgentRules.description": "Bật tải tệp AGENTS.md cho các quy tắc dành riêng cho tác nhân (xem https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "Thời gian tối đa tính bằng giây để đợi phản hồi API (0 = không có thời gian chờ, 1-3600 giây, mặc định: 600 giây). Nên sử dụng các giá trị cao hơn cho các nhà cung cấp cục bộ như LM Studio và Ollama có thể cần thêm thời gian xử lý."
}
diff --git a/src/package.nls.zh-CN.json b/src/package.nls.zh-CN.json
index b93e022334..1ea96e772a 100644
--- a/src/package.nls.zh-CN.json
+++ b/src/package.nls.zh-CN.json
@@ -23,6 +23,7 @@
"command.history.title": "历史记录",
"command.marketplace.title": "应用市场",
"command.openInEditor.title": "在编辑器中打开",
+ "command.account.title": "账户",
"command.settings.title": "设置",
"command.documentation.title": "文档",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "自定义存储路径。留空以使用默认位置。支持绝对路径(例如:'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "启用 Roo Code 快速修复",
"settings.autoImportSettingsPath.description": "RooCode 配置文件的路径,用于在扩展启动时自动导入。支持绝对路径和相对于主目录的路径(例如 '~/Documents/roo-code-settings.json')。留空以禁用自动导入。",
- "settings.useAgentRules.description": "为特定于代理的规则启用 AGENTS.md 文件的加载(请参阅 https://agent-rules.org/)"
+ "settings.useAgentRules.description": "为特定于代理的规则启用 AGENTS.md 文件的加载(请参阅 https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "等待 API 响应的最长时间(秒)(0 = 无超时,1-3600秒,默认值:600秒)。对于像 LM Studio 和 Ollama 这样可能需要更多处理时间的本地提供商,建议使用更高的值。"
}
diff --git a/src/package.nls.zh-TW.json b/src/package.nls.zh-TW.json
index 42aef592bd..815a061160 100644
--- a/src/package.nls.zh-TW.json
+++ b/src/package.nls.zh-TW.json
@@ -23,6 +23,7 @@
"command.history.title": "歷史記錄",
"command.marketplace.title": "應用市場",
"command.openInEditor.title": "在編輯器中開啟",
+ "command.account.title": "帳戶",
"command.settings.title": "設定",
"command.documentation.title": "文件",
"configuration.title": "Roo Code",
@@ -36,5 +37,6 @@
"settings.customStoragePath.description": "自訂儲存路徑。留空以使用預設位置。支援絕對路徑(例如:'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "啟用 Roo Code 快速修復。",
"settings.autoImportSettingsPath.description": "RooCode 設定檔案的路徑,用於在擴充功能啟動時自動匯入。支援絕對路徑和相對於主目錄的路徑(例如 '~/Documents/roo-code-settings.json')。留空以停用自動匯入。",
- "settings.useAgentRules.description": "為特定於代理的規則啟用 AGENTS.md 檔案的載入(請參閱 https://agent-rules.org/)"
+ "settings.useAgentRules.description": "為特定於代理的規則啟用 AGENTS.md 檔案的載入(請參閱 https://agent-rules.org/)",
+ "settings.apiRequestTimeout.description": "等待 API 回應的最長時間(秒)(0 = 無超時,1-3600秒,預設值:600秒)。對於像 LM Studio 和 Ollama 這樣可能需要更多處理時間的本地提供商,建議使用更高的值。"
}
diff --git a/src/services/browser/UrlContentFetcher.ts b/src/services/browser/UrlContentFetcher.ts
index 8f4de7fbb4..b271bc2ef4 100644
--- a/src/services/browser/UrlContentFetcher.ts
+++ b/src/services/browser/UrlContentFetcher.ts
@@ -50,15 +50,20 @@ export class UrlContentFetcher {
return
}
const stats = await this.ensureChromiumExists()
+ const args = [
+ "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
+ "--disable-dev-shm-usage",
+ "--disable-accelerated-2d-canvas",
+ "--no-first-run",
+ "--disable-gpu",
+ "--disable-features=VizDisplayCompositor",
+ ]
+ if (process.platform === "linux") {
+ // Fixes network errors on Linux hosts (see https://github.com/puppeteer/puppeteer/issues/8246)
+ args.push("--no-sandbox")
+ }
this.browser = await stats.puppeteer.launch({
- args: [
- "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
- "--disable-dev-shm-usage",
- "--disable-accelerated-2d-canvas",
- "--no-first-run",
- "--disable-gpu",
- "--disable-features=VizDisplayCompositor",
- ],
+ args,
executablePath: stats.executablePath,
})
// (latest version of puppeteer does not add headless to user agent)
diff --git a/src/services/browser/__tests__/UrlContentFetcher.spec.ts b/src/services/browser/__tests__/UrlContentFetcher.spec.ts
index 132d73a409..f280263a02 100644
--- a/src/services/browser/__tests__/UrlContentFetcher.spec.ts
+++ b/src/services/browser/__tests__/UrlContentFetcher.spec.ts
@@ -125,25 +125,75 @@ describe("UrlContentFetcher", () => {
})
describe("launchBrowser", () => {
- it("should launch browser with correct arguments", async () => {
- await urlContentFetcher.launchBrowser()
-
- expect(vi.mocked(PCR)).toHaveBeenCalledWith({
- downloadPath: path.join("/test/storage", "puppeteer"),
+ it("should launch browser with correct arguments on non-Linux platforms", async () => {
+ // Ensure we're not on Linux for this test
+ const originalPlatform = process.platform
+ Object.defineProperty(process, "platform", {
+ value: "darwin", // macOS
})
- const stats = await vi.mocked(PCR).mock.results[0].value
- expect(stats.puppeteer.launch).toHaveBeenCalledWith({
- args: [
- "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
- "--disable-dev-shm-usage",
- "--disable-accelerated-2d-canvas",
- "--no-first-run",
- "--disable-gpu",
- "--disable-features=VizDisplayCompositor",
- ],
- executablePath: "/path/to/chromium",
+ try {
+ await urlContentFetcher.launchBrowser()
+
+ expect(vi.mocked(PCR)).toHaveBeenCalledWith({
+ downloadPath: path.join("/test/storage", "puppeteer"),
+ })
+
+ const stats = await vi.mocked(PCR).mock.results[0].value
+ expect(stats.puppeteer.launch).toHaveBeenCalledWith({
+ args: [
+ "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
+ "--disable-dev-shm-usage",
+ "--disable-accelerated-2d-canvas",
+ "--no-first-run",
+ "--disable-gpu",
+ "--disable-features=VizDisplayCompositor",
+ ],
+ executablePath: "/path/to/chromium",
+ })
+ } finally {
+ // Restore original platform
+ Object.defineProperty(process, "platform", {
+ value: originalPlatform,
+ })
+ }
+ })
+
+ it("should launch browser with Linux-specific arguments", async () => {
+ // Mock process.platform to be linux
+ const originalPlatform = process.platform
+ Object.defineProperty(process, "platform", {
+ value: "linux",
})
+
+ try {
+ // Create a new instance to ensure fresh state
+ const linuxFetcher = new UrlContentFetcher(mockContext)
+ await linuxFetcher.launchBrowser()
+
+ expect(vi.mocked(PCR)).toHaveBeenCalledWith({
+ downloadPath: path.join("/test/storage", "puppeteer"),
+ })
+
+ const stats = await vi.mocked(PCR).mock.results[vi.mocked(PCR).mock.results.length - 1].value
+ expect(stats.puppeteer.launch).toHaveBeenCalledWith({
+ args: [
+ "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
+ "--disable-dev-shm-usage",
+ "--disable-accelerated-2d-canvas",
+ "--no-first-run",
+ "--disable-gpu",
+ "--disable-features=VizDisplayCompositor",
+ "--no-sandbox", // Linux-specific argument
+ ],
+ executablePath: "/path/to/chromium",
+ })
+ } finally {
+ // Restore original platform
+ Object.defineProperty(process, "platform", {
+ value: originalPlatform,
+ })
+ }
})
it("should set viewport and headers after launching", async () => {
diff --git a/src/services/code-index/__tests__/manager.spec.ts b/src/services/code-index/__tests__/manager.spec.ts
index 49f725f69b..929f6f93c8 100644
--- a/src/services/code-index/__tests__/manager.spec.ts
+++ b/src/services/code-index/__tests__/manager.spec.ts
@@ -19,7 +19,14 @@ vi.mock("vscode", () => {
index: 0,
},
],
+ createFileSystemWatcher: vi.fn().mockReturnValue({
+ onDidCreate: vi.fn().mockReturnValue({ dispose: vi.fn() }),
+ onDidChange: vi.fn().mockReturnValue({ dispose: vi.fn() }),
+ onDidDelete: vi.fn().mockReturnValue({ dispose: vi.fn() }),
+ dispose: vi.fn(),
+ }),
},
+ RelativePattern: vi.fn().mockImplementation((base, pattern) => ({ base, pattern })),
}
})
@@ -32,6 +39,26 @@ vi.mock("../../../utils/path", () => {
}
})
+// Mock fs/promises for RooIgnoreController
+vi.mock("fs/promises", () => ({
+ default: {
+ readFile: vi.fn().mockRejectedValue(new Error("File not found")), // Simulate no .gitignore/.rooignore
+ },
+}))
+
+// Mock file utils for RooIgnoreController
+vi.mock("../../../utils/fs", () => ({
+ fileExistsAtPath: vi.fn().mockResolvedValue(false), // Simulate no .rooignore file
+}))
+
+// Mock ignore module
+vi.mock("ignore", () => ({
+ default: vi.fn().mockReturnValue({
+ add: vi.fn(),
+ ignores: vi.fn().mockReturnValue(false),
+ }),
+}))
+
vi.mock("../state-manager", () => ({
CodeIndexStateManager: vi.fn().mockImplementation(() => ({
onProgressUpdate: vi.fn(),
diff --git a/src/services/code-index/manager.ts b/src/services/code-index/manager.ts
index 1257b747c6..d82760533d 100644
--- a/src/services/code-index/manager.ts
+++ b/src/services/code-index/manager.ts
@@ -9,6 +9,7 @@ import { CodeIndexServiceFactory } from "./service-factory"
import { CodeIndexSearchService } from "./search-service"
import { CodeIndexOrchestrator } from "./orchestrator"
import { CacheManager } from "./cache-manager"
+import { RooIgnoreController } from "../../core/ignore/RooIgnoreController"
import fs from "fs/promises"
import ignore from "ignore"
import path from "path"
@@ -312,6 +313,7 @@ export class CodeIndexManager {
return
}
+ // Create .gitignore instance
const ignorePath = path.join(workspacePath, ".gitignore")
try {
const content = await fs.readFile(ignorePath, "utf8")
@@ -327,11 +329,16 @@ export class CodeIndexManager {
})
}
+ // Create RooIgnoreController instance
+ const rooIgnoreController = new RooIgnoreController(workspacePath)
+ await rooIgnoreController.initialize()
+
// (Re)Create shared service instances
const { embedder, vectorStore, scanner, fileWatcher } = this._serviceFactory.createServices(
this.context,
this._cacheManager!,
ignoreInstance,
+ rooIgnoreController,
)
// Validate embedder configuration before proceeding
diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts
index 68b0f5c0bc..5c1c366107 100644
--- a/src/services/code-index/service-factory.ts
+++ b/src/services/code-index/service-factory.ts
@@ -10,6 +10,7 @@ import { codeParser, DirectoryScanner, FileWatcher } from "./processors"
import { ICodeParser, IEmbedder, IFileWatcher, IVectorStore } from "./interfaces"
import { CodeIndexConfigManager } from "./config-manager"
import { CacheManager } from "./cache-manager"
+import { RooIgnoreController } from "../../core/ignore/RooIgnoreController"
import { Ignore } from "ignore"
import { t } from "../../i18n"
import { TelemetryService } from "@roo-code/telemetry"
@@ -161,8 +162,17 @@ export class CodeIndexServiceFactory {
vectorStore: IVectorStore,
cacheManager: CacheManager,
ignoreInstance: Ignore,
+ rooIgnoreController?: RooIgnoreController,
): IFileWatcher {
- return new FileWatcher(this.workspacePath, context, cacheManager, embedder, vectorStore, ignoreInstance)
+ return new FileWatcher(
+ this.workspacePath,
+ context,
+ cacheManager,
+ embedder,
+ vectorStore,
+ ignoreInstance,
+ rooIgnoreController,
+ )
}
/**
@@ -173,6 +183,7 @@ export class CodeIndexServiceFactory {
context: vscode.ExtensionContext,
cacheManager: CacheManager,
ignoreInstance: Ignore,
+ rooIgnoreController?: RooIgnoreController,
): {
embedder: IEmbedder
vectorStore: IVectorStore
@@ -188,7 +199,14 @@ export class CodeIndexServiceFactory {
const vectorStore = this.createVectorStore()
const parser = codeParser
const scanner = this.createDirectoryScanner(embedder, vectorStore, parser, ignoreInstance)
- const fileWatcher = this.createFileWatcher(context, embedder, vectorStore, cacheManager, ignoreInstance)
+ const fileWatcher = this.createFileWatcher(
+ context,
+ embedder,
+ vectorStore,
+ cacheManager,
+ ignoreInstance,
+ rooIgnoreController,
+ )
return {
embedder,
diff --git a/src/services/marketplace/MarketplaceManager.ts b/src/services/marketplace/MarketplaceManager.ts
index 800eba62e0..0227cdb79d 100644
--- a/src/services/marketplace/MarketplaceManager.ts
+++ b/src/services/marketplace/MarketplaceManager.ts
@@ -4,9 +4,9 @@ import * as path from "path"
import * as vscode from "vscode"
import * as yaml from "yaml"
-import type { MarketplaceItem, MarketplaceItemType, McpMarketplaceItem, OrganizationSettings } from "@roo-code/types"
+import type { MarketplaceItem, MarketplaceItemType, McpMarketplaceItem } from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
-import { CloudService } from "@roo-code/cloud"
+import { type OrganizationSettings, CloudService } from "@roo-code/cloud"
import { GlobalFileNames } from "../../shared/globalFileNames"
import { ensureSettingsDirectoryExists } from "../../utils/globalContext"
diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts
index 646236b5d5..271c6e1fb3 100644
--- a/src/services/mcp/McpHub.ts
+++ b/src/services/mcp/McpHub.ts
@@ -1212,7 +1212,6 @@ export class McpHub {
public async refreshAllConnections(): Promise {
if (this.isConnecting) {
- vscode.window.showInformationMessage(t("mcp:info.already_refreshing"))
return
}
@@ -1234,7 +1233,6 @@ export class McpHub {
}
this.isConnecting = true
- vscode.window.showInformationMessage(t("mcp:info.refreshing_all"))
try {
const globalPath = await this.getMcpSettingsFilePath()
@@ -1244,11 +1242,6 @@ export class McpHub {
const globalConfig = JSON.parse(globalContent)
globalServers = globalConfig.mcpServers || {}
const globalServerNames = Object.keys(globalServers)
- vscode.window.showInformationMessage(
- t("mcp:info.global_servers_active", {
- mcpServers: `${globalServerNames.join(", ") || "none"}`,
- }),
- )
} catch (error) {
console.log("Error reading global MCP config:", error)
}
@@ -1261,11 +1254,6 @@ export class McpHub {
const projectConfig = JSON.parse(projectContent)
projectServers = projectConfig.mcpServers || {}
const projectServerNames = Object.keys(projectServers)
- vscode.window.showInformationMessage(
- t("mcp:info.project_servers_active", {
- mcpServers: `${projectServerNames.join(", ") || "none"}`,
- }),
- )
} catch (error) {
console.log("Error reading project MCP config:", error)
}
@@ -1285,8 +1273,6 @@ export class McpHub {
await delay(100)
await this.notifyWebviewOfServerChanges()
-
- vscode.window.showInformationMessage(t("mcp:info.all_refreshed"))
} catch (error) {
this.showErrorMessage("Failed to refresh MCP servers", error)
} finally {
diff --git a/src/services/ripgrep/index.ts b/src/services/ripgrep/index.ts
index c694221966..d384b27c91 100644
--- a/src/services/ripgrep/index.ts
+++ b/src/services/ripgrep/index.ts
@@ -150,7 +150,7 @@ export async function regexSearchFiles(
throw new Error("Could not find ripgrep binary")
}
- const args = ["--json", "-e", regex, "--glob", filePattern || "*", "--context", "1", directoryPath]
+ const args = ["--json", "-e", regex, "--glob", filePattern || "*", "--context", "1", "--no-messages", directoryPath]
let output: string
try {
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index 3ddd69945c..ebdc137432 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -7,17 +7,15 @@ import type {
TelemetrySetting,
Experiments,
ClineMessage,
- OrganizationAllowList,
- CloudUserInfo,
- ShareVisibility,
+ MarketplaceItem,
} from "@roo-code/types"
+import type { CloudUserInfo, OrganizationAllowList, ShareVisibility } from "@roo-code/cloud"
import { GitCommit } from "../utils/git"
import { McpServer } from "./mcp"
import { Mode } from "./modes"
import { ModelRecord, RouterModels } from "./api"
-import type { MarketplaceItem } from "@roo-code/types"
// Command interface for frontend/backend communication
export interface Command {
@@ -271,6 +269,7 @@ export type ExtensionState = Pick<
| "profileThresholds"
| "includeDiagnosticMessages"
| "maxDiagnosticMessages"
+ | "remoteControlEnabled"
> & {
version: string
clineMessages: ClineMessage[]
diff --git a/src/shared/ProfileValidator.ts b/src/shared/ProfileValidator.ts
index 3dc8025ff8..afdf1b5232 100644
--- a/src/shared/ProfileValidator.ts
+++ b/src/shared/ProfileValidator.ts
@@ -1,4 +1,5 @@
-import { OrganizationAllowList, ProviderSettings } from "@roo-code/types"
+import type { ProviderSettings } from "@roo-code/types"
+import type { OrganizationAllowList } from "@roo-code/cloud"
export class ProfileValidator {
public static isProfileAllowed(profile: ProviderSettings, allowList: OrganizationAllowList): boolean {
@@ -87,6 +88,8 @@ export class ProfileValidator {
return profile.ollamaModelId
case "requesty":
return profile.requestyModelId
+ case "io-intelligence":
+ return profile.ioIntelligenceModelId
case "human-relay":
case "fake-ai":
default:
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index cb8759d851..d59ccd556c 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -1,14 +1,14 @@
import { z } from "zod"
-import type {
- ProviderSettings,
- PromptComponent,
- ModeConfig,
- InstallMarketplaceItemOptions,
- MarketplaceItem,
- ShareVisibility,
+import {
+ type ProviderSettings,
+ type PromptComponent,
+ type ModeConfig,
+ type InstallMarketplaceItemOptions,
+ type MarketplaceItem,
+ marketplaceItemSchema,
} from "@roo-code/types"
-import { marketplaceItemSchema } from "@roo-code/types"
+import type { ShareVisibility } from "@roo-code/cloud"
import { Mode } from "./modes"
@@ -130,6 +130,7 @@ export interface WebviewMessage {
| "terminalCompressProgressBar"
| "mcpEnabled"
| "enableMcpServerCreation"
+ | "remoteControlEnabled"
| "searchCommits"
| "alwaysApproveResubmit"
| "requestDelaySeconds"
diff --git a/src/shared/__tests__/ProfileValidator.spec.ts b/src/shared/__tests__/ProfileValidator.spec.ts
index fa055a8157..0129f8fc20 100644
--- a/src/shared/__tests__/ProfileValidator.spec.ts
+++ b/src/shared/__tests__/ProfileValidator.spec.ts
@@ -1,6 +1,7 @@
// npx vitest run src/shared/__tests__/ProfileValidator.spec.ts
-import { OrganizationAllowList, ProviderSettings } from "@roo-code/types"
+import { type ProviderSettings } from "@roo-code/types"
+import { type OrganizationAllowList } from "@roo-code/cloud"
import { ProfileValidator } from "../ProfileValidator"
@@ -229,6 +230,22 @@ describe("ProfileValidator", () => {
expect(ProfileValidator.isProfileAllowed(profile, allowList)).toBe(true)
})
+ // Test for io-intelligence provider which uses ioIntelligenceModelId
+ it(`should extract ioIntelligenceModelId for io-intelligence provider`, () => {
+ const allowList: OrganizationAllowList = {
+ allowAll: false,
+ providers: {
+ "io-intelligence": { allowAll: false, models: ["test-model"] },
+ },
+ }
+ const profile: ProviderSettings = {
+ apiProvider: "io-intelligence" as any,
+ ioIntelligenceModelId: "test-model",
+ }
+
+ expect(ProfileValidator.isProfileAllowed(profile, allowList)).toBe(true)
+ })
+
it("should extract vsCodeLmModelSelector.id for vscode-lm provider", () => {
const allowList: OrganizationAllowList = {
allowAll: false,
diff --git a/src/shared/__tests__/api.spec.ts b/src/shared/__tests__/api.spec.ts
index 80c3db1b7d..a56fea8e14 100644
--- a/src/shared/__tests__/api.spec.ts
+++ b/src/shared/__tests__/api.spec.ts
@@ -190,6 +190,95 @@ describe("getModelMaxOutputTokens", () => {
expect(result).toBe(20_000) // Should use model.maxTokens since it's exactly at 20%
})
+ test("should bypass 20% cap for GPT-5 models and use exact configured max tokens", () => {
+ const model: ModelInfo = {
+ contextWindow: 200_000,
+ supportsPromptCache: false,
+ maxTokens: 128_000, // 64% of context window, normally would be capped
+ }
+
+ const settings: ProviderSettings = {
+ apiProvider: "openai",
+ }
+
+ // Test various GPT-5 model IDs
+ const gpt5ModelIds = ["gpt-5", "gpt-5-turbo", "GPT-5", "openai/gpt-5-preview", "gpt-5-32k", "GPT-5-TURBO"]
+
+ gpt5ModelIds.forEach((modelId) => {
+ const result = getModelMaxOutputTokens({
+ modelId,
+ model,
+ settings,
+ format: "openai",
+ })
+ // Should use full 128k tokens, not capped to 20% (40k)
+ expect(result).toBe(128_000)
+ })
+ })
+
+ test("should still apply 20% cap to non-GPT-5 models", () => {
+ const model: ModelInfo = {
+ contextWindow: 200_000,
+ supportsPromptCache: false,
+ maxTokens: 128_000, // 64% of context window, should be capped
+ }
+
+ const settings: ProviderSettings = {
+ apiProvider: "openai",
+ }
+
+ // Test non-GPT-5 model IDs
+ const nonGpt5ModelIds = ["gpt-4", "gpt-4-turbo", "gpt-3.5-turbo", "claude-3-5-sonnet", "gemini-pro"]
+
+ nonGpt5ModelIds.forEach((modelId) => {
+ const result = getModelMaxOutputTokens({
+ modelId,
+ model,
+ settings,
+ format: "openai",
+ })
+ // Should be capped to 20% of context window: 200_000 * 0.2 = 40_000
+ expect(result).toBe(40_000)
+ })
+ })
+
+ test("should handle GPT-5 models with various max token configurations", () => {
+ const testCases = [
+ {
+ maxTokens: 128_000,
+ contextWindow: 200_000,
+ expected: 128_000, // Uses full 128k
+ },
+ {
+ maxTokens: 64_000,
+ contextWindow: 200_000,
+ expected: 64_000, // Uses configured 64k
+ },
+ {
+ maxTokens: 256_000,
+ contextWindow: 400_000,
+ expected: 256_000, // Uses full 256k even though it's 64% of context
+ },
+ ]
+
+ testCases.forEach(({ maxTokens, contextWindow, expected }) => {
+ const model: ModelInfo = {
+ contextWindow,
+ supportsPromptCache: false,
+ maxTokens,
+ }
+
+ const result = getModelMaxOutputTokens({
+ modelId: "gpt-5-turbo",
+ model,
+ settings: { apiProvider: "openai" },
+ format: "openai",
+ })
+
+ expect(result).toBe(expected)
+ })
+ })
+
test("should return modelMaxTokens from settings when reasoning budget is required", () => {
const model: ModelInfo = {
contextWindow: 200_000,
diff --git a/src/shared/api.ts b/src/shared/api.ts
index 014b903453..f1bf7dbaea 100644
--- a/src/shared/api.ts
+++ b/src/shared/api.ts
@@ -6,12 +6,28 @@ import {
} from "@roo-code/types"
// ApiHandlerOptions
-
-export type ApiHandlerOptions = Omit
+// Extend ProviderSettings (minus apiProvider) with handler-specific toggles.
+export type ApiHandlerOptions = Omit & {
+ /**
+ * When true and using GPT‑5 Responses API, include reasoning.summary: "auto"
+ * so the API returns reasoning summaries (we already parse and surface them).
+ * Defaults to true; set to false to disable summaries.
+ */
+ enableGpt5ReasoningSummary?: boolean
+}
// RouterName
-const routerNames = ["openrouter", "requesty", "glama", "unbound", "litellm", "ollama", "lmstudio"] as const
+const routerNames = [
+ "openrouter",
+ "requesty",
+ "glama",
+ "unbound",
+ "litellm",
+ "ollama",
+ "lmstudio",
+ "io-intelligence",
+] as const
export type RouterName = (typeof routerNames)[number]
@@ -91,7 +107,17 @@ export const getModelMaxOutputTokens = ({
}
// If model has explicit maxTokens, clamp it to 20% of the context window
+ // Exception: GPT-5 models should use their exact configured max output tokens
if (model.maxTokens) {
+ // Check if this is a GPT-5 model (case-insensitive)
+ const isGpt5Model = modelId.toLowerCase().includes("gpt-5")
+
+ // GPT-5 models bypass the 20% cap and use their full configured max tokens
+ if (isGpt5Model) {
+ return model.maxTokens
+ }
+
+ // All other models are clamped to 20% of context window
return Math.min(model.maxTokens, Math.ceil(model.contextWindow * 0.2))
}
@@ -114,3 +140,4 @@ export type GetModelsOptions =
| { provider: "litellm"; apiKey: string; baseUrl: string }
| { provider: "ollama"; baseUrl?: string }
| { provider: "lmstudio"; baseUrl?: string }
+ | { provider: "io-intelligence"; apiKey: string }
diff --git a/src/shared/cloud.ts b/src/shared/cloud.ts
new file mode 100644
index 0000000000..9adf9294a0
--- /dev/null
+++ b/src/shared/cloud.ts
@@ -0,0 +1,8 @@
+import { CloudUserInfo, OrganizationAllowList, ShareVisibility } from "@roo-code/cloud"
+
+export type { CloudUserInfo, OrganizationAllowList, ShareVisibility }
+
+export const ORGANIZATION_ALLOW_ALL: OrganizationAllowList = {
+ allowAll: true,
+ providers: {},
+} as const
diff --git a/src/utils/__tests__/xml.spec.ts b/src/utils/__tests__/xml.spec.ts
index 0e43cf04a1..f7a282b0c0 100644
--- a/src/utils/__tests__/xml.spec.ts
+++ b/src/utils/__tests__/xml.spec.ts
@@ -1,4 +1,4 @@
-import { parseXml } from "../xml"
+import { parseXml, parseXmlForDiff } from "../xml"
describe("parseXml", () => {
describe("type conversion", () => {
@@ -115,3 +115,126 @@ describe("parseXml", () => {
})
})
})
+
+describe("parseXmlForDiff", () => {
+ describe("HTML entity handling", () => {
+ it("should NOT decode HTML entities like &", () => {
+ const xml = `
+
+ Team Identity & Project Positioning
+
+ `
+
+ const result = parseXmlForDiff(xml) as any
+
+ // The & should remain as-is, not be decoded to &
+ expect(result.root.content).toBe("Team Identity & Project Positioning")
+ })
+
+ it("should preserve & character without encoding", () => {
+ const xml = `
+
+ Team Identity & Project Positioning
+
+ `
+
+ const result = parseXmlForDiff(xml) as any
+
+ // The & should remain as-is
+ expect(result.root.content).toBe("Team Identity & Project Positioning")
+ })
+
+ it("should NOT decode other HTML entities", () => {
+ const xml = `
+
+ <div> "Hello" 'World'
+
+ `
+
+ const result = parseXmlForDiff(xml) as any
+
+ // All HTML entities should remain as-is
+ expect(result.root.content).toBe("<div> "Hello" 'World'")
+ })
+
+ it("should handle mixed content with entities correctly", () => {
+ const xml = `
+
+ if (a < b && c > d) { return "test"; }
+
+ `
+
+ const result = parseXmlForDiff(xml) as any
+
+ // All entities should remain unchanged
+ expect(result.root.code).toBe("if (a < b && c > d) { return "test"; }")
+ })
+ })
+
+ describe("basic functionality (same as parseXml)", () => {
+ it("should correctly parse a simple XML string", () => {
+ const xml = `
+
+ Test Name
+ Some description
+
+ `
+
+ const result = parseXmlForDiff(xml) as any
+
+ expect(result).toHaveProperty("root")
+ expect(result.root).toHaveProperty("name", "Test Name")
+ expect(result.root).toHaveProperty("description", "Some description")
+ })
+
+ it("should handle attributes correctly", () => {
+ const xml = `
+
+ - Item content
+
+ `
+
+ const result = parseXmlForDiff(xml) as any
+
+ expect(result.root.item).toHaveProperty("@_id", "1")
+ expect(result.root.item).toHaveProperty("@_category", "test")
+ expect(result.root.item).toHaveProperty("#text", "Item content")
+ })
+
+ it("should support stopNodes parameter", () => {
+ const xml = `
+
+
+ - Should not parse this
+
+
+ `
+
+ const result = parseXmlForDiff(xml, ["nestedXml"]) as any
+
+ expect(result.root.data.nestedXml).toBeTruthy()
+ expect(result.root.data.nestedXml).toHaveProperty("item", "Should not parse this")
+ })
+ })
+
+ describe("diff-specific use case", () => {
+ it("should preserve exact content for diff matching", () => {
+ // This simulates the actual use case from the issue
+ const xml = `
+
+
+ ./doc.md
+
+ Team Identity & Project Positioning
+
+
+
+ `
+
+ const result = parseXmlForDiff(xml, ["file.diff.content"]) as any
+
+ // The & should remain as-is for exact matching with file content
+ expect(result.args.file.diff.content).toBe("Team Identity & Project Positioning")
+ })
+ })
+})
diff --git a/src/utils/remoteControl.ts b/src/utils/remoteControl.ts
new file mode 100644
index 0000000000..e645ce7219
--- /dev/null
+++ b/src/utils/remoteControl.ts
@@ -0,0 +1,11 @@
+import type { CloudUserInfo } from "@roo-code/cloud"
+
+/**
+ * Determines if remote control features should be enabled
+ * @param cloudUserInfo - User information from cloud service
+ * @param remoteControlEnabled - User's remote control setting
+ * @returns true if remote control should be enabled
+ */
+export function isRemoteControlEnabled(cloudUserInfo?: CloudUserInfo | null, remoteControlEnabled?: boolean): boolean {
+ return !!(cloudUserInfo?.id && cloudUserInfo.extensionBridgeEnabled && remoteControlEnabled)
+}
diff --git a/src/utils/xml.ts b/src/utils/xml.ts
index 0fd6ef574c..f183309d49 100644
--- a/src/utils/xml.ts
+++ b/src/utils/xml.ts
@@ -1,13 +1,28 @@
import { XMLParser } from "fast-xml-parser"
+/**
+ * Options for XML parsing
+ */
+interface ParseXmlOptions {
+ /**
+ * Whether to process HTML entities (e.g., & to &).
+ * Default: true for general parsing, false for diff operations
+ */
+ processEntities?: boolean
+}
+
/**
* Parses an XML string into a JavaScript object
* @param xmlString The XML string to parse
+ * @param stopNodes Optional array of node names to stop parsing at
+ * @param options Optional parsing options
* @returns Parsed JavaScript object representation of the XML
* @throws Error if the XML is invalid or parsing fails
*/
-export function parseXml(xmlString: string, stopNodes?: string[]): unknown {
+export function parseXml(xmlString: string, stopNodes?: string[], options?: ParseXmlOptions): unknown {
const _stopNodes = stopNodes ?? []
+ const processEntities = options?.processEntities ?? true
+
try {
const parser = new XMLParser({
ignoreAttributes: false,
@@ -15,6 +30,7 @@ export function parseXml(xmlString: string, stopNodes?: string[]): unknown {
parseAttributeValue: false,
parseTagValue: false,
trimValues: true,
+ processEntities,
stopNodes: _stopNodes,
})
@@ -25,3 +41,23 @@ export function parseXml(xmlString: string, stopNodes?: string[]): unknown {
throw new Error(`Failed to parse XML: ${errorMessage}`)
}
}
+
+/**
+ * Parses an XML string for diffing purposes, ensuring no HTML entities are decoded.
+ * This is a specialized version of parseXml to be used exclusively by diffing tools
+ * to prevent mismatches caused by entity processing.
+ *
+ * Use this instead of parseXml when:
+ * - Comparing parsed content against original file content
+ * - Performing diff operations where exact character matching is required
+ * - Processing XML that will be used in search/replace operations
+ *
+ * @param xmlString The XML string to parse
+ * @param stopNodes Optional array of node names to stop parsing at
+ * @returns Parsed JavaScript object representation of the XML
+ * @throws Error if the XML is invalid or parsing fails
+ */
+export function parseXmlForDiff(xmlString: string, stopNodes?: string[]): unknown {
+ // Delegate to parseXml with processEntities disabled
+ return parseXml(xmlString, stopNodes, { processEntities: false })
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000..44f734c197
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@roo-code/config-typescript/base.json",
+ "compilerOptions": {
+ "types": ["node"]
+ },
+ "exclude": ["node_modules"],
+ "include": ["scripts/*.ts"]
+}
diff --git a/webview-ui/src/components/account/AccountView.tsx b/webview-ui/src/components/account/AccountView.tsx
index e3d1a293a7..2e75b2d1c6 100644
--- a/webview-ui/src/components/account/AccountView.tsx
+++ b/webview-ui/src/components/account/AccountView.tsx
@@ -1,12 +1,17 @@
import { useEffect, useRef } from "react"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
-import type { CloudUserInfo } from "@roo-code/types"
import { TelemetryEventName } from "@roo-code/types"
+import type { CloudUserInfo } from "@roo/cloud"
+
import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { useExtensionState } from "@src/context/ExtensionStateContext"
import { vscode } from "@src/utils/vscode"
import { telemetryClient } from "@src/utils/TelemetryClient"
+import { ToggleSwitch } from "@/components/ui/toggle-switch"
+
+import { History, PiggyBank, SquareArrowOutUpRightIcon } from "lucide-react"
type AccountViewProps = {
userInfo: CloudUserInfo | null
@@ -17,6 +22,7 @@ type AccountViewProps = {
export const AccountView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: AccountViewProps) => {
const { t } = useAppTranslation()
+ const { remoteControlEnabled, setRemoteControlEnabled } = useExtensionState()
const wasAuthenticatedRef = useRef(false)
const rooLogoUri = (window as any).IMAGES_BASE_URI + "/roo-logo.svg"
@@ -51,11 +57,17 @@ export const AccountView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }:
vscode.postMessage({ type: "openExternal", url: cloudUrl })
}
+ const handleRemoteControlToggle = () => {
+ const newValue = !remoteControlEnabled
+ setRemoteControlEnabled(newValue)
+ vscode.postMessage({ type: "remoteControlEnabled", bool: newValue })
+ }
+
return (
-
+
{t("account:title")}
-
+
{t("settings:common.done")}
@@ -77,13 +89,13 @@ export const AccountView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }:
)}
{userInfo.name && (
-
{userInfo.name}
+
{userInfo.name}
)}
{userInfo?.email && (
-
{userInfo?.email}
+
{userInfo?.email}
)}
{userInfo?.organizationName && (
-
+
{userInfo.organizationImageUrl && (

)}
+
+ {userInfo?.extensionBridgeEnabled && (
+
+
+
+ {t("account:remoteControl")}
+
+
+ {t("account:remoteControlDescription")}
+
+
+
+ )}
+
{t("account:visitCloudWebsite")}
@@ -125,30 +157,27 @@ export const AccountView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }:
-
+
{t("account:cloudBenefitsTitle")}
-
- {t("account:cloudBenefitsSubtitle")}
-
-
- -
- •
- {t("account:cloudBenefitHistory")}
-
- -
- •
+
+ -
+
{t("account:cloudBenefitSharing")}
- -
- •
+
-
+
+ {t("account:cloudBenefitHistory")}
+
+ -
+
{t("account:cloudBenefitMetrics")}
-
-
+
+
{t("account:connect")}
diff --git a/webview-ui/src/components/account/__tests__/AccountView.spec.tsx b/webview-ui/src/components/account/__tests__/AccountView.spec.tsx
index d6fd3013e6..c960ca4662 100644
--- a/webview-ui/src/components/account/__tests__/AccountView.spec.tsx
+++ b/webview-ui/src/components/account/__tests__/AccountView.spec.tsx
@@ -11,11 +11,16 @@ vi.mock("@src/i18n/TranslationContext", () => ({
"settings:common.done": "Done",
"account:signIn": "Connect to Roo Code Cloud",
"account:cloudBenefitsTitle": "Connect to Roo Code Cloud",
- "account:cloudBenefitsSubtitle": "Sync your prompts and telemetry to enable:",
- "account:cloudBenefitHistory": "Online task history",
- "account:cloudBenefitSharing": "Sharing and collaboration features",
- "account:cloudBenefitMetrics": "Task, token, and cost-based usage metrics",
+ "account:cloudBenefitSharing": "Share tasks with others",
+ "account:cloudBenefitHistory": "Access your task history",
+ "account:cloudBenefitMetrics": "Get a holistic view of your token consumption",
"account:logOut": "Log out",
+ "account:connect": "Connect Now",
+ "account:visitCloudWebsite": "Visit Roo Code Cloud",
+ "account:remoteControl": "Roomote Control",
+ "account:remoteControlDescription":
+ "Enable following and interacting with tasks in this workspace with Roo Code Cloud",
+ "account:profilePicture": "Profile picture",
}
return translations[key] || key
},
@@ -36,6 +41,14 @@ vi.mock("@src/utils/TelemetryClient", () => ({
},
}))
+// Mock the extension state context
+vi.mock("@src/context/ExtensionStateContext", () => ({
+ useExtensionState: () => ({
+ remoteControlEnabled: false,
+ setRemoteControlEnabled: vi.fn(),
+ }),
+}))
+
// Mock window global for images
Object.defineProperty(window, "IMAGES_BASE_URI", {
value: "/images",
@@ -55,13 +68,12 @@ describe("AccountView", () => {
// Check that the benefits section is displayed
expect(screen.getByRole("heading", { name: "Connect to Roo Code Cloud" })).toBeInTheDocument()
- expect(screen.getByText("Sync your prompts and telemetry to enable:")).toBeInTheDocument()
- expect(screen.getByText("Online task history")).toBeInTheDocument()
- expect(screen.getByText("Sharing and collaboration features")).toBeInTheDocument()
- expect(screen.getByText("Task, token, and cost-based usage metrics")).toBeInTheDocument()
+ expect(screen.getByText("Share tasks with others")).toBeInTheDocument()
+ expect(screen.getByText("Access your task history")).toBeInTheDocument()
+ expect(screen.getByText("Get a holistic view of your token consumption")).toBeInTheDocument()
// Check that the connect button is also present
- expect(screen.getByText("account:connect")).toBeInTheDocument()
+ expect(screen.getByText("Connect Now")).toBeInTheDocument()
})
it("should not display benefits when user is authenticated", () => {
@@ -80,13 +92,60 @@ describe("AccountView", () => {
)
// Check that the benefits section is NOT displayed
- expect(screen.queryByText("Sync your prompts and telemetry to enable:")).not.toBeInTheDocument()
- expect(screen.queryByText("Online task history")).not.toBeInTheDocument()
- expect(screen.queryByText("Sharing and collaboration features")).not.toBeInTheDocument()
- expect(screen.queryByText("Task, token, and cost-based usage metrics")).not.toBeInTheDocument()
+ expect(
+ screen.queryByText("Follow and control tasks from anywhere with Roomote Control"),
+ ).not.toBeInTheDocument()
+ expect(screen.queryByText("Share tasks with others")).not.toBeInTheDocument()
+ expect(screen.queryByText("Access your task history")).not.toBeInTheDocument()
+ expect(screen.queryByText("Get a holistic view of your token consumption")).not.toBeInTheDocument()
// Check that user info is displayed instead
expect(screen.getByText("Test User")).toBeInTheDocument()
expect(screen.getByText("test@example.com")).toBeInTheDocument()
})
+
+ it("should display remote control toggle when user has extension bridge enabled", () => {
+ const mockUserInfo = {
+ name: "Test User",
+ email: "test@example.com",
+ extensionBridgeEnabled: true,
+ }
+
+ render(
+ {}}
+ />,
+ )
+
+ // Check that the remote control toggle is displayed
+ expect(screen.getByTestId("remote-control-toggle")).toBeInTheDocument()
+ expect(screen.getByText("Roomote Control")).toBeInTheDocument()
+ expect(
+ screen.getByText("Enable following and interacting with tasks in this workspace with Roo Code Cloud"),
+ ).toBeInTheDocument()
+ })
+
+ it("should not display remote control toggle when user does not have extension bridge enabled", () => {
+ const mockUserInfo = {
+ name: "Test User",
+ email: "test@example.com",
+ extensionBridgeEnabled: false,
+ }
+
+ render(
+ {}}
+ />,
+ )
+
+ // Check that the remote control toggle is NOT displayed
+ expect(screen.queryByTestId("remote-control-toggle")).not.toBeInTheDocument()
+ expect(screen.queryByText("Roomote Control")).not.toBeInTheDocument()
+ })
})
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index 9b8c96dea1..3b77bbdb7d 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -434,9 +434,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction {
+ // Reset UI states
setExpandedRows({})
everVisibleMessagesTsRef.current.clear() // Clear for new task
setCurrentFollowUpTs(null) // Clear follow-up answered state for new task
+ setIsCondensing(false) // Reset condensing state when switching tasks
+ // Note: sendingDisabled is not reset here as it's managed by message effects
// Clear any pending auto-approval timeout from previous task
if (autoApproveTimeoutRef.current) {
@@ -897,11 +900,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction textAreaRef.current?.focus())
const visibleMessages = useMemo(() => {
- const currentMessageCount = modifiedMessages.length
- const startIndex = Math.max(0, currentMessageCount - 500)
- const recentMessages = modifiedMessages.slice(startIndex)
-
- const newVisibleMessages = recentMessages.filter((message: ClineMessage) => {
+ // Remove the 500-message limit to prevent array index shifting
+ // Virtuoso is designed to efficiently handle large lists through virtualization
+ const newVisibleMessages = modifiedMessages.filter((message: ClineMessage) => {
if (everVisibleMessagesTsRef.current.has(message.ts)) {
const alwaysHiddenOnceProcessedAsk: ClineAsk[] = [
"api_req_failed",
diff --git a/webview-ui/src/components/chat/ShareButton.tsx b/webview-ui/src/components/chat/ShareButton.tsx
index 04cb1e2b38..4a0559fec7 100644
--- a/webview-ui/src/components/chat/ShareButton.tsx
+++ b/webview-ui/src/components/chat/ShareButton.tsx
@@ -2,8 +2,9 @@ import { useState, useEffect, useRef } from "react"
import { useTranslation } from "react-i18next"
import { SquareArrowOutUpRightIcon } from "lucide-react"
-import type { HistoryItem, ShareVisibility } from "@roo-code/types"
-import { TelemetryEventName } from "@roo-code/types"
+import { type HistoryItem, TelemetryEventName } from "@roo-code/types"
+
+import type { ShareVisibility } from "@roo/cloud"
import { vscode } from "@/utils/vscode"
import { telemetryClient } from "@/utils/TelemetryClient"
diff --git a/webview-ui/src/components/settings/ApiConfigManager.tsx b/webview-ui/src/components/settings/ApiConfigManager.tsx
index e737678c01..0d18f2a55b 100644
--- a/webview-ui/src/components/settings/ApiConfigManager.tsx
+++ b/webview-ui/src/components/settings/ApiConfigManager.tsx
@@ -2,11 +2,21 @@ import { memo, useEffect, useRef, useState } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { AlertTriangle } from "lucide-react"
-import type { ProviderSettingsEntry, OrganizationAllowList } from "@roo-code/types"
+import type { ProviderSettingsEntry } from "@roo-code/types"
+
+import type { OrganizationAllowList } from "@roo/cloud"
import { useAppTranslation } from "@/i18n/TranslationContext"
-import { Button, Input, Dialog, DialogContent, DialogTitle, StandardTooltip, SearchableSelect } from "@/components/ui"
-import type { SearchableSelectOption } from "@/components/ui"
+import {
+ type SearchableSelectOption,
+ Button,
+ Input,
+ Dialog,
+ DialogContent,
+ DialogTitle,
+ StandardTooltip,
+ SearchableSelect,
+} from "@/components/ui"
interface ApiConfigManagerProps {
currentApiConfigName?: string
diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx
index 74ba885d25..b51b171354 100644
--- a/webview-ui/src/components/settings/ApiOptions.tsx
+++ b/webview-ui/src/components/settings/ApiOptions.tsx
@@ -31,6 +31,7 @@ import {
internationalZAiDefaultModelId,
mainlandZAiDefaultModelId,
fireworksDefaultModelId,
+ ioIntelligenceDefaultModelId,
} from "@roo-code/types"
import { vscode } from "@src/utils/vscode"
@@ -68,6 +69,7 @@ import {
Glama,
Groq,
HuggingFace,
+ IOIntelligence,
LMStudio,
LiteLLM,
Mistral,
@@ -103,7 +105,11 @@ import { buildDocLink } from "@src/utils/docLinks"
export interface ApiOptionsProps {
uriScheme: string | undefined
apiConfiguration: ProviderSettings
- setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void
+ setApiConfigurationField: (
+ field: K,
+ value: ProviderSettings[K],
+ isUserAction?: boolean,
+ ) => void
fromWelcomeView?: boolean
errorMessage: string | undefined
setErrorMessage: React.Dispatch>
@@ -278,7 +284,7 @@ const ApiOptions = ({
const shouldSetDefault = !modelId
if (shouldSetDefault) {
- setApiConfigurationField(field, defaultValue)
+ setApiConfigurationField(field, defaultValue, false)
}
}
@@ -320,6 +326,7 @@ const ApiOptions = ({
: internationalZAiDefaultModelId,
},
fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
+ "io-intelligence": { field: "ioIntelligenceModelId", default: ioIntelligenceDefaultModelId },
openai: { field: "openAiModelId" },
ollama: { field: "ollamaModelId" },
lmstudio: { field: "lmStudioModelId" },
@@ -548,6 +555,15 @@ const ApiOptions = ({
)}
+ {selectedProvider === "io-intelligence" && (
+
+ )}
+
{selectedProvider === "human-relay" && (
<>
@@ -576,6 +592,12 @@ const ApiOptions = ({
if (value !== "custom-arn" && selectedProvider === "bedrock") {
setApiConfigurationField("awsCustomArn", "")
}
+
+ // Clear reasoning effort when switching models to allow the new model's default to take effect
+ // This is especially important for GPT-5 models which default to "medium"
+ if (selectedProvider === "openai-native") {
+ setApiConfigurationField("reasoningEffort", undefined)
+ }
}}>
@@ -617,11 +639,14 @@ const ApiOptions = ({
modelInfo={selectedModelInfo}
/>
-
+ {/* Gate Verbosity UI by capability flag */}
+ {selectedModelInfo?.supportsVerbosity && (
+
+ )}
{!fromWelcomeView && (
diff --git a/webview-ui/src/components/settings/ModelInfoView.tsx b/webview-ui/src/components/settings/ModelInfoView.tsx
index 8078b03acd..2ba732effa 100644
--- a/webview-ui/src/components/settings/ModelInfoView.tsx
+++ b/webview-ui/src/components/settings/ModelInfoView.tsx
@@ -41,6 +41,12 @@ export const ModelInfoView = ({
supportsLabel={t("settings:modelInfo.supportsPromptCache")}
doesNotSupportLabel={t("settings:modelInfo.noPromptCache")}
/>,
+ typeof modelInfo?.contextWindow === "number" && modelInfo.contextWindow > 0 && (
+ <>
+ {t("settings:modelInfo.contextWindow")}{" "}
+ {modelInfo.contextWindow?.toLocaleString()} tokens
+ >
+ ),
typeof modelInfo?.maxTokens === "number" && modelInfo.maxTokens > 0 && (
<>
{t("settings:modelInfo.maxOutput")}:{" "}
diff --git a/webview-ui/src/components/settings/ModelPicker.tsx b/webview-ui/src/components/settings/ModelPicker.tsx
index c8445766f1..0753f9fc2b 100644
--- a/webview-ui/src/components/settings/ModelPicker.tsx
+++ b/webview-ui/src/components/settings/ModelPicker.tsx
@@ -3,7 +3,9 @@ import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { Trans } from "react-i18next"
import { ChevronsUpDown, Check, X } from "lucide-react"
-import type { ProviderSettings, ModelInfo, OrganizationAllowList } from "@roo-code/types"
+import type { ProviderSettings, ModelInfo } from "@roo-code/types"
+
+import type { OrganizationAllowList } from "@roo/cloud"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { useSelectedModel } from "@/components/ui/hooks/useSelectedModel"
@@ -28,7 +30,13 @@ import { ApiErrorMessage } from "./ApiErrorMessage"
type ModelIdKey = keyof Pick<
ProviderSettings,
- "glamaModelId" | "openRouterModelId" | "unboundModelId" | "requestyModelId" | "openAiModelId" | "litellmModelId"
+ | "glamaModelId"
+ | "openRouterModelId"
+ | "unboundModelId"
+ | "requestyModelId"
+ | "openAiModelId"
+ | "litellmModelId"
+ | "ioIntelligenceModelId"
>
interface ModelPickerProps {
@@ -38,7 +46,11 @@ interface ModelPickerProps {
serviceName: string
serviceUrl: string
apiConfiguration: ProviderSettings
- setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void
+ setApiConfigurationField: (
+ field: K,
+ value: ProviderSettings[K],
+ isUserAction?: boolean,
+ ) => void
organizationAllowList: OrganizationAllowList
errorMessage?: string
}
@@ -116,7 +128,7 @@ export const ModelPicker = ({
useEffect(() => {
if (!selectedModelId && !isInitialized.current) {
const initialValue = modelIds.includes(selectedModelId) ? selectedModelId : defaultModelId
- setApiConfigurationField(modelIdKey, initialValue)
+ setApiConfigurationField(modelIdKey, initialValue, false) // false = automatic initialization
}
isInitialized.current = true
@@ -149,7 +161,7 @@ export const ModelPicker = ({
aria-expanded={open}
className="w-full justify-between"
data-testid="model-picker-button">
- {selectedModelId ?? t("settings:common.select")}
+ {selectedModelId ?? t("settings:common.select")}
@@ -188,7 +200,9 @@ export const ModelPicker = ({
value={model}
onSelect={onSelect}
data-testid={`model-option-${model}`}>
- {model}
+
+ {model}
+
(({ onDone, t
}, [])
const setApiConfigurationField = useCallback(
- (field: K, value: ProviderSettings[K]) => {
+ (field: K, value: ProviderSettings[K], isUserAction: boolean = true) => {
setCachedState((prevState) => {
if (prevState.apiConfiguration?.[field] === value) {
return prevState
@@ -227,9 +227,9 @@ const SettingsView = forwardRef(({ onDone, t
const previousValue = prevState.apiConfiguration?.[field]
- // Don't treat initial sync from undefined to a defined value as a user change
- // This prevents the dirty state when the component initializes and auto-syncs the model ID
- const isInitialSync = previousValue === undefined && value !== undefined
+ // Only skip change detection for automatic initialization (not user actions)
+ // This prevents the dirty state when the component initializes and auto-syncs values
+ const isInitialSync = !isUserAction && previousValue === undefined && value !== undefined
if (!isInitialSync) {
setChangeDetected(true)
@@ -629,6 +629,8 @@ const SettingsView = forwardRef(({ onDone, t
alwaysAllowUpdateTodoList={alwaysAllowUpdateTodoList}
followupAutoApproveTimeoutMs={followupAutoApproveTimeoutMs}
allowedCommands={allowedCommands}
+ allowedMaxRequests={allowedMaxRequests ?? undefined}
+ allowedMaxCost={allowedMaxCost ?? undefined}
deniedCommands={deniedCommands}
setCachedStateField={setCachedStateField}
/>
diff --git a/webview-ui/src/components/settings/ThinkingBudget.tsx b/webview-ui/src/components/settings/ThinkingBudget.tsx
index a49ec79efc..7a85e61e7a 100644
--- a/webview-ui/src/components/settings/ThinkingBudget.tsx
+++ b/webview-ui/src/components/settings/ThinkingBudget.tsx
@@ -1,7 +1,12 @@
import { useEffect } from "react"
import { Checkbox } from "vscrui"
-import { type ProviderSettings, type ModelInfo, type ReasoningEffort, reasoningEfforts } from "@roo-code/types"
+import {
+ type ProviderSettings,
+ type ModelInfo,
+ type ReasoningEffortWithMinimal,
+ reasoningEfforts,
+} from "@roo-code/types"
import {
DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS,
@@ -15,10 +20,25 @@ import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
interface ThinkingBudgetProps {
apiConfiguration: ProviderSettings
- setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void
+ setApiConfigurationField: (
+ field: K,
+ value: ProviderSettings[K],
+ isUserAction?: boolean,
+ ) => void
modelInfo?: ModelInfo
}
+// Helper function to determine if minimal option should be shown
+const shouldShowMinimalOption = (
+ provider: string | undefined,
+ modelId: string | undefined,
+ supportsEffort: boolean | undefined,
+): boolean => {
+ const isGpt5Model = provider === "openai-native" && modelId?.startsWith("gpt-5")
+ const isOpenRouterWithEffort = provider === "openrouter" && supportsEffort === true
+ return !!(isGpt5Model || isOpenRouterWithEffort)
+}
+
export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, modelInfo }: ThinkingBudgetProps) => {
const { t } = useAppTranslation()
const { id: selectedModelId } = useSelectedModel(apiConfiguration)
@@ -27,10 +47,38 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
const isGemini25Pro = selectedModelId && selectedModelId.includes("gemini-2.5-pro")
const minThinkingTokens = isGemini25Pro ? GEMINI_25_PRO_MIN_THINKING_TOKENS : 1024
+ // Check model capabilities
const isReasoningBudgetSupported = !!modelInfo && modelInfo.supportsReasoningBudget
const isReasoningBudgetRequired = !!modelInfo && modelInfo.requiredReasoningBudget
const isReasoningEffortSupported = !!modelInfo && modelInfo.supportsReasoningEffort
+ // Determine if minimal option should be shown
+ const showMinimalOption = shouldShowMinimalOption(
+ apiConfiguration.apiProvider,
+ selectedModelId,
+ isReasoningEffortSupported,
+ )
+
+ // Build available reasoning efforts list
+ const baseEfforts = [...reasoningEfforts] as ReasoningEffortWithMinimal[]
+ const availableReasoningEfforts: ReadonlyArray = showMinimalOption
+ ? (["minimal", ...baseEfforts] as ReasoningEffortWithMinimal[])
+ : baseEfforts
+
+ // Default reasoning effort - use model's default if available
+ // GPT-5 models have "medium" as their default in the model configuration
+ const modelDefaultReasoningEffort = modelInfo?.reasoningEffort as ReasoningEffortWithMinimal | undefined
+ const defaultReasoningEffort: ReasoningEffortWithMinimal = modelDefaultReasoningEffort || "medium"
+ const currentReasoningEffort: ReasoningEffortWithMinimal =
+ (apiConfiguration.reasoningEffort as ReasoningEffortWithMinimal | undefined) || defaultReasoningEffort
+
+ // Set default reasoning effort when model supports it and no value is set
+ useEffect(() => {
+ if (isReasoningEffortSupported && !apiConfiguration.reasoningEffort && defaultReasoningEffort) {
+ setApiConfigurationField("reasoningEffort", defaultReasoningEffort, false)
+ }
+ }, [isReasoningEffortSupported, apiConfiguration.reasoningEffort, defaultReasoningEffort, setApiConfigurationField])
+
const enableReasoningEffort = apiConfiguration.enableReasoningEffort
const customMaxOutputTokens = apiConfiguration.modelMaxTokens || DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS
const customMaxThinkingTokens =
@@ -47,7 +95,7 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
// appropriately.
useEffect(() => {
if (isReasoningBudgetSupported && customMaxThinkingTokens > modelMaxThinkingTokens) {
- setApiConfigurationField("modelMaxThinkingTokens", modelMaxThinkingTokens)
+ setApiConfigurationField("modelMaxThinkingTokens", modelMaxThinkingTokens, false)
}
}, [isReasoningBudgetSupported, customMaxThinkingTokens, modelMaxThinkingTokens, setApiConfigurationField])
@@ -109,13 +157,21 @@ export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, mod
+ {supports1MContextBeta && (
+
+
{
+ setApiConfigurationField("anthropicBeta1MContext", checked)
+ }}>
+ {t("settings:providers.anthropic1MContextBetaLabel")}
+
+
+ {t("settings:providers.anthropic1MContextBetaDescription")}
+
+
+ )}
>
)
}
diff --git a/webview-ui/src/components/settings/providers/Bedrock.tsx b/webview-ui/src/components/settings/providers/Bedrock.tsx
index 750f631856..6c871e570a 100644
--- a/webview-ui/src/components/settings/providers/Bedrock.tsx
+++ b/webview-ui/src/components/settings/providers/Bedrock.tsx
@@ -2,7 +2,12 @@ import { useCallback, useState, useEffect } from "react"
import { Checkbox } from "vscrui"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
-import { type ProviderSettings, type ModelInfo, BEDROCK_REGIONS } from "@roo-code/types"
+import {
+ type ProviderSettings,
+ type ModelInfo,
+ BEDROCK_REGIONS,
+ BEDROCK_CLAUDE_SONNET_4_MODEL_ID,
+} from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StandardTooltip } from "@src/components/ui"
@@ -19,6 +24,9 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo
const { t } = useAppTranslation()
const [awsEndpointSelected, setAwsEndpointSelected] = useState(!!apiConfiguration?.awsBedrockEndpointEnabled)
+ // Check if the selected model supports 1M context (Claude Sonnet 4)
+ const supports1MContextBeta = apiConfiguration?.apiModelId === BEDROCK_CLAUDE_SONNET_4_MODEL_ID
+
// Update the endpoint enabled state when the configuration changes
useEffect(() => {
setAwsEndpointSelected(!!apiConfiguration?.awsBedrockEndpointEnabled)
@@ -159,6 +167,20 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo
>
)}
+ {supports1MContextBeta && (
+
+
{
+ setApiConfigurationField("awsBedrock1MContext", checked)
+ }}>
+ {t("settings:providers.awsBedrock1MContextBetaLabel")}
+
+
+ {t("settings:providers.awsBedrock1MContextBetaDescription")}
+
+
+ )}
{
diff --git a/webview-ui/src/components/settings/providers/Glama.tsx b/webview-ui/src/components/settings/providers/Glama.tsx
index ca1c6590ef..bc3fb71cae 100644
--- a/webview-ui/src/components/settings/providers/Glama.tsx
+++ b/webview-ui/src/components/settings/providers/Glama.tsx
@@ -1,8 +1,9 @@
import { useCallback } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
-import { type ProviderSettings, type OrganizationAllowList, glamaDefaultModelId } from "@roo-code/types"
+import { type ProviderSettings, glamaDefaultModelId } from "@roo-code/types"
+import type { OrganizationAllowList } from "@roo/cloud"
import type { RouterModels } from "@roo/api"
import { useAppTranslation } from "@src/i18n/TranslationContext"
diff --git a/webview-ui/src/components/settings/providers/HuggingFace.tsx b/webview-ui/src/components/settings/providers/HuggingFace.tsx
index 8716739d80..94f60cc4c8 100644
--- a/webview-ui/src/components/settings/providers/HuggingFace.tsx
+++ b/webview-ui/src/components/settings/providers/HuggingFace.tsx
@@ -34,7 +34,11 @@ type HuggingFaceModel = {
type HuggingFaceProps = {
apiConfiguration: ProviderSettings
- setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
+ setApiConfigurationField: (
+ field: keyof ProviderSettings,
+ value: ProviderSettings[keyof ProviderSettings],
+ isUserAction?: boolean,
+ ) => void
}
export const HuggingFace = ({ apiConfiguration, setApiConfigurationField }: HuggingFaceProps) => {
@@ -93,7 +97,7 @@ export const HuggingFace = ({ apiConfiguration, setApiConfigurationField }: Hugg
// Set to "auto" as default
const defaultProvider = "auto"
setSelectedProvider(defaultProvider)
- setApiConfigurationField("huggingFaceInferenceProvider", defaultProvider)
+ setApiConfigurationField("huggingFaceInferenceProvider", defaultProvider, false) // false = automatic default
}
}
}
diff --git a/webview-ui/src/components/settings/providers/IOIntelligence.tsx b/webview-ui/src/components/settings/providers/IOIntelligence.tsx
new file mode 100644
index 0000000000..81e0d546f4
--- /dev/null
+++ b/webview-ui/src/components/settings/providers/IOIntelligence.tsx
@@ -0,0 +1,74 @@
+import { useCallback } from "react"
+import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
+
+import { type ProviderSettings, ioIntelligenceDefaultModelId, ioIntelligenceModels } from "@roo-code/types"
+
+import type { OrganizationAllowList } from "@roo/cloud"
+
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
+import { useExtensionState } from "@src/context/ExtensionStateContext"
+
+import { ModelPicker } from "../ModelPicker"
+
+import { inputEventTransform } from "../transforms"
+
+type IOIntelligenceProps = {
+ apiConfiguration: ProviderSettings
+ setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
+ organizationAllowList: OrganizationAllowList
+ modelValidationError?: string
+}
+
+export const IOIntelligence = ({
+ apiConfiguration,
+ setApiConfigurationField,
+ organizationAllowList,
+ modelValidationError,
+}: IOIntelligenceProps) => {
+ const { t } = useAppTranslation()
+ const { routerModels } = useExtensionState()
+
+ const handleInputChange = useCallback(
+ (
+ field: K,
+ transform: (event: E) => ProviderSettings[K] = inputEventTransform,
+ ) =>
+ (event: E | Event) => {
+ setApiConfigurationField(field, transform(event as E))
+ },
+ [setApiConfigurationField],
+ )
+
+ return (
+ <>
+
+
+
+
+ {t("settings:providers.apiKeyStorageNotice")}
+
+ {!apiConfiguration?.ioIntelligenceApiKey && (
+
+ {t("settings:providers.getIoIntelligenceApiKey")}
+
+ )}
+
+ >
+ )
+}
diff --git a/webview-ui/src/components/settings/providers/LiteLLM.tsx b/webview-ui/src/components/settings/providers/LiteLLM.tsx
index caf7a173fe..0cb3245ea4 100644
--- a/webview-ui/src/components/settings/providers/LiteLLM.tsx
+++ b/webview-ui/src/components/settings/providers/LiteLLM.tsx
@@ -1,8 +1,9 @@
import { useCallback, useState, useEffect, useRef } from "react"
import { VSCodeTextField, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
-import { type ProviderSettings, type OrganizationAllowList, litellmDefaultModelId } from "@roo-code/types"
+import { type ProviderSettings, litellmDefaultModelId } from "@roo-code/types"
+import type { OrganizationAllowList } from "@roo/cloud"
import { RouterName } from "@roo/api"
import { ExtensionMessage } from "@roo/ExtensionMessage"
diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx
index 736b0253c4..ee462296b5 100644
--- a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx
+++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx
@@ -7,11 +7,11 @@ import {
type ProviderSettings,
type ModelInfo,
type ReasoningEffort,
- type OrganizationAllowList,
azureOpenAiDefaultApiVersion,
openAiModelInfoSaneDefaults,
} from "@roo-code/types"
+import type { OrganizationAllowList } from "@roo/cloud"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { useAppTranslation } from "@src/i18n/TranslationContext"
diff --git a/webview-ui/src/components/settings/providers/OpenRouter.tsx b/webview-ui/src/components/settings/providers/OpenRouter.tsx
index f6cad36bf8..cf8b54d0cd 100644
--- a/webview-ui/src/components/settings/providers/OpenRouter.tsx
+++ b/webview-ui/src/components/settings/providers/OpenRouter.tsx
@@ -3,8 +3,9 @@ import { Trans } from "react-i18next"
import { Checkbox } from "vscrui"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
-import { type ProviderSettings, type OrganizationAllowList, openRouterDefaultModelId } from "@roo-code/types"
+import { type ProviderSettings, openRouterDefaultModelId } from "@roo-code/types"
+import type { OrganizationAllowList } from "@roo/cloud"
import type { RouterModels } from "@roo/api"
import { useAppTranslation } from "@src/i18n/TranslationContext"
diff --git a/webview-ui/src/components/settings/providers/Requesty.tsx b/webview-ui/src/components/settings/providers/Requesty.tsx
index ac9e2735e9..7586b1e084 100644
--- a/webview-ui/src/components/settings/providers/Requesty.tsx
+++ b/webview-ui/src/components/settings/providers/Requesty.tsx
@@ -1,8 +1,9 @@
-import { useCallback, useState } from "react"
-import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
+import { useCallback, useEffect, useState } from "react"
+import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
-import { type ProviderSettings, type OrganizationAllowList, requestyDefaultModelId } from "@roo-code/types"
+import { type ProviderSettings, requestyDefaultModelId } from "@roo-code/types"
+import type { OrganizationAllowList } from "@roo/cloud"
import type { RouterModels } from "@roo/api"
import { vscode } from "@src/utils/vscode"
@@ -35,6 +36,13 @@ export const Requesty = ({
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.
+ useEffect(() => {
+ setRequestyEndpointSelected(!!apiConfiguration?.requestyBaseUrl)
+ }, [apiConfiguration?.requestyBaseUrl])
+
const handleInputChange = useCallback(
(
field: K,
@@ -72,6 +80,31 @@ export const Requesty = ({
{t("settings:providers.getRequestyApiKey")}
)}
+
+ {
+ const isChecked = e.target.checked === true
+ if (!isChecked) {
+ setApiConfigurationField("requestyBaseUrl", undefined)
+ }
+
+ setRequestyEndpointSelected(isChecked)
+ }}>
+ {t("settings:providers.requestyUseCustomBaseUrl")}
+
+ {requestyEndpointSelected && (
+
+
+
+
+
+ )}