PNPM + Turbo monorepo + Nightly releases (#3407)
1
.gitattributes
vendored
|
|
@ -1,2 +1,3 @@
|
|||
demo.gif filter=lfs diff=lfs merge=lfs -text
|
||||
assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text
|
||||
src/assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
|
|||
83
.github/actions/ai-release-notes/action.yml
vendored
|
|
@ -1,83 +0,0 @@
|
|||
name: AI Release Notes
|
||||
description: Generate AI release notes using git and openai, outputs 'RELEASE_NOTES' and 'OPENAI_PROMPT'
|
||||
|
||||
inputs:
|
||||
OPENAI_API_KEY:
|
||||
required: true
|
||||
type: string
|
||||
GHA_PAT:
|
||||
required: true
|
||||
type: string
|
||||
model_name:
|
||||
required: false
|
||||
type: string
|
||||
default: gpt-4o-mini
|
||||
repo_path:
|
||||
required: false
|
||||
type: string
|
||||
custom_prompt:
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
git_ref:
|
||||
required: true
|
||||
type: string
|
||||
head_ref:
|
||||
required: true
|
||||
type: string
|
||||
base_ref:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
outputs:
|
||||
RELEASE_NOTES:
|
||||
description: "AI generated release notes"
|
||||
value: ${{ steps.ai_release_notes.outputs.RELEASE_NOTES }}
|
||||
OPENAI_PROMPT:
|
||||
description: "Prompt used to generate release notes"
|
||||
value: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }}
|
||||
|
||||
env:
|
||||
GITHUB_REF: ${{ inputs.git_ref }}
|
||||
BASE_REF: ${{ inputs.base_ref }}
|
||||
HEAD_REF: ${{ inputs.head_ref }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ inputs.repo_path }}
|
||||
token: ${{ inputs.GHA_PAT }}
|
||||
ref: ${{ env.GITHUB_REF }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set Workspace
|
||||
shell: bash
|
||||
run: |
|
||||
pip install tiktoken
|
||||
pip install pytz
|
||||
|
||||
# Github outputs: 'OPENAI_PROMPT'
|
||||
- name: Add Git Info to base prompt
|
||||
id: ai_prompt
|
||||
shell: bash
|
||||
env:
|
||||
BASE_REF: ${{ env.BASE_REF }}
|
||||
HEAD_SHA: ${{ env.HEAD_SHA }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
MODEL_NAME: ${{ inputs.model_name }}
|
||||
CUSTOM_PROMPT: ${{ inputs.custom_prompt }} # Default: ''
|
||||
run: python .github/scripts/release-notes-prompt.py
|
||||
|
||||
# Github outputs: 'RELEASE_NOTES'
|
||||
- name: Generate AI release notes
|
||||
id: ai_release_notes
|
||||
shell: bash
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ inputs.OPENAI_API_KEY }}
|
||||
CUSTOM_PROMPT: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }}
|
||||
MODEL_NAME: ${{ inputs.model_name }}
|
||||
run: python .github/scripts/ai-release-notes.py
|
||||
|
||||
123
.github/scripts/ai-release-notes.py
vendored
|
|
@ -1,123 +0,0 @@
|
|||
"""
|
||||
AI-powered release notes generator that creates concise and informative release notes from git changes.
|
||||
|
||||
This script uses OpenAI's API to analyze git changes (summary, diff, and commit log) and generate
|
||||
well-formatted release notes in markdown. It focuses on important changes and their impact,
|
||||
particularly highlighting new types and schemas while avoiding repetitive information.
|
||||
|
||||
Environment Variables Required:
|
||||
OPENAI_API_KEY: OpenAI API key for authentication
|
||||
CHANGE_SUMMARY: Summary of changes made (optional if CUSTOM_PROMPT provided)
|
||||
CHANGE_DIFF: Git diff of changes (optional if CUSTOM_PROMPT provided)
|
||||
CHANGE_LOG: Git commit log (optional if CUSTOM_PROMPT provided)
|
||||
GITHUB_OUTPUT: Path to GitHub output file
|
||||
CUSTOM_PROMPT: Custom prompt to override default (optional)
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests # type: ignore
|
||||
import json
|
||||
import tiktoken # type: ignore
|
||||
|
||||
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
|
||||
CHANGE_SUMMARY = os.environ.get('CHANGE_SUMMARY', '')
|
||||
CHANGE_DIFF = os.environ.get('CHANGE_DIFF', '')
|
||||
CHANGE_LOG = os.environ.get('CHANGE_LOG', '')
|
||||
GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT")
|
||||
OPEN_AI_BASE_URL = "https://api.openai.com/v1"
|
||||
OPEN_API_HEADERS = {"Authorization": f"Bearer {OPENAI_API_KEY}", "Content-Type": "application/json"}
|
||||
CUSTOM_PROMPT = os.environ.get('CUSTOM_PROMPT', '')
|
||||
MODEL_NAME = os.environ.get('MODEL_NAME', 'gpt-3.5-turbo-16k')
|
||||
|
||||
def num_tokens_from_string(string: str, model_name: str) -> int:
|
||||
"""
|
||||
Calculate the number of tokens in a text string for a specific model.
|
||||
|
||||
Args:
|
||||
string: The input text to count tokens for
|
||||
model_name: Name of the OpenAI model to use for token counting
|
||||
|
||||
Returns:
|
||||
int: Number of tokens in the input string
|
||||
"""
|
||||
encoding = tiktoken.encoding_for_model(model_name)
|
||||
num_tokens = len(encoding.encode(string))
|
||||
return num_tokens
|
||||
|
||||
def truncate_to_token_limit(text, max_tokens, model_name):
|
||||
"""
|
||||
Truncate text to fit within a maximum token limit for a specific model.
|
||||
|
||||
Args:
|
||||
text: The input text to truncate
|
||||
max_tokens: Maximum number of tokens allowed
|
||||
model_name: Name of the OpenAI model to use for tokenization
|
||||
|
||||
Returns:
|
||||
str: Truncated text that fits within the token limit
|
||||
"""
|
||||
encoding = tiktoken.encoding_for_model(model_name)
|
||||
encoded = encoding.encode(text)
|
||||
truncated = encoded[:max_tokens]
|
||||
return encoding.decode(truncated)
|
||||
|
||||
def generate_release_notes(model_name):
|
||||
"""
|
||||
Generate release notes using OpenAI's API based on git changes.
|
||||
|
||||
Uses the GPT-3.5-turbo model to analyze change summary, commit log, and code diff
|
||||
to generate concise and informative release notes in markdown format. The notes
|
||||
focus on important changes and their impact, with sections for new types/schemas
|
||||
and other updates.
|
||||
|
||||
Returns:
|
||||
str: Generated release notes in markdown format
|
||||
|
||||
Raises:
|
||||
requests.exceptions.RequestException: If the OpenAI API request fails
|
||||
"""
|
||||
max_tokens = 14000 # Reserve some tokens for the response
|
||||
|
||||
# Truncate inputs if necessary to fit within token limits
|
||||
change_summary = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_SUMMARY, 1000, model_name)
|
||||
change_log = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_LOG, 2000, model_name)
|
||||
change_diff = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_DIFF, max_tokens - num_tokens_from_string(change_summary, model_name) - num_tokens_from_string(change_log, model_name) - 1000, model_name)
|
||||
|
||||
url = f"{OPEN_AI_BASE_URL}/chat/completions"
|
||||
|
||||
# Construct prompt for OpenAI API
|
||||
openai_prompt = CUSTOM_PROMPT if CUSTOM_PROMPT else f"""Based on the following summary of changes, commit log and code diff, please generate concise and informative release notes:
|
||||
Summary of changes:
|
||||
{change_summary}
|
||||
Commit log:
|
||||
{change_log}
|
||||
Code Diff:
|
||||
{json.dumps(change_diff)}
|
||||
"""
|
||||
|
||||
data = {
|
||||
"model": model_name,
|
||||
"messages": [{"role": "user", "content": openai_prompt}],
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 1000,
|
||||
}
|
||||
|
||||
print("----------------------------------------------------------------------------------------------------------")
|
||||
print("POST request to OpenAI")
|
||||
print("----------------------------------------------------------------------------------------------------------")
|
||||
ai_response = requests.post(url, headers=OPEN_API_HEADERS, json=data)
|
||||
print(f"Status Code: {str(ai_response.status_code)}")
|
||||
print(f"Response: {ai_response.text}")
|
||||
ai_response.raise_for_status()
|
||||
|
||||
return ai_response.json()["choices"][0]["message"]["content"]
|
||||
|
||||
release_notes = generate_release_notes(MODEL_NAME)
|
||||
print("----------------------------------------------------------------------------------------------------------")
|
||||
print("OpenAI generated release notes")
|
||||
print("----------------------------------------------------------------------------------------------------------")
|
||||
print(release_notes)
|
||||
|
||||
# Write the release notes to GITHUB_OUTPUT
|
||||
with open(GITHUB_OUTPUT, "a") as outputs_file:
|
||||
outputs_file.write(f"RELEASE_NOTES<<EOF\n{release_notes}\nEOF")
|
||||
52
.github/scripts/get_prev_version_refs.py
vendored
|
|
@ -1,52 +0,0 @@
|
|||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
def run_git_command(command):
|
||||
result = subprocess.getoutput(command)
|
||||
print(f"Git Command: {command}")
|
||||
print(f"Git Output: {result}")
|
||||
return result
|
||||
|
||||
def parse_merge_commit(line):
|
||||
# Parse merge commit messages like:
|
||||
# "355dc82 Merge pull request #71 from RooCodeInc/better-error-handling"
|
||||
pattern = r"([a-f0-9]+)\s+Merge pull request #(\d+) from (.+)"
|
||||
match = re.match(pattern, line)
|
||||
if match:
|
||||
sha, pr_number, branch = match.groups()
|
||||
return {
|
||||
'sha': sha,
|
||||
'pr_number': pr_number,
|
||||
'branch': branch
|
||||
}
|
||||
return None
|
||||
|
||||
def get_version_refs():
|
||||
# Get the merge commits with full message
|
||||
command = 'git log --merges --pretty=oneline -n 3'
|
||||
result = run_git_command(command)
|
||||
|
||||
if result:
|
||||
commits = result.split('\n')
|
||||
if len(commits) >= 3:
|
||||
# Parse HEAD~1 (PR to generate notes for)
|
||||
head_info = parse_merge_commit(commits[1])
|
||||
# Parse HEAD~2 (previous PR to compare against)
|
||||
base_info = parse_merge_commit(commits[2])
|
||||
|
||||
if head_info and base_info:
|
||||
# Set output for GitHub Actions
|
||||
with open(os.environ['GITHUB_OUTPUT'], 'a') as gha_outputs:
|
||||
gha_outputs.write(f"head_ref={head_info['sha']}\n")
|
||||
gha_outputs.write(f"base_ref={base_info['sha']}")
|
||||
|
||||
print(f"Head ref (PR #{head_info['pr_number']}): {head_info['sha']}")
|
||||
print(f"Base ref (PR #{base_info['pr_number']}): {base_info['sha']}")
|
||||
return head_info, base_info
|
||||
|
||||
print("Could not find or parse sufficient merge history")
|
||||
return None, None
|
||||
|
||||
if __name__ == "__main__":
|
||||
head_info, base_info = get_version_refs()
|
||||
62
.github/scripts/overwrite_changeset_changelog.py
vendored
|
|
@ -1,62 +0,0 @@
|
|||
"""
|
||||
This script updates a specific version's release notes section in CHANGELOG.md with new content
|
||||
or reformats existing content.
|
||||
|
||||
The script:
|
||||
1. Takes a version number, changelog path, and optionally new content as input from environment variables
|
||||
2. Finds the section in the changelog for the specified version
|
||||
3. Either:
|
||||
a) Replaces the content with new content if provided, or
|
||||
b) Reformats existing content by:
|
||||
- Removing the first two lines of the changeset format
|
||||
- Ensuring version numbers are wrapped in square brackets
|
||||
4. Writes the updated changelog back to the file
|
||||
|
||||
Environment Variables:
|
||||
CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md')
|
||||
VERSION: The version number to update/format
|
||||
PREV_VERSION: The previous version number (used to locate section boundaries)
|
||||
NEW_CONTENT: Optional new content to insert for this version
|
||||
"""
|
||||
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
|
||||
CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md")
|
||||
VERSION = os.environ['VERSION']
|
||||
PREV_VERSION = os.environ.get("PREV_VERSION", "")
|
||||
NEW_CONTENT = os.environ.get("NEW_CONTENT", "")
|
||||
|
||||
def overwrite_changelog_section(changelog_text: str, new_content: str):
|
||||
# Find the section for the specified version
|
||||
version_pattern = f"## {VERSION}\n"
|
||||
prev_version_pattern = f"## [{PREV_VERSION}]\n"
|
||||
print(f"latest version: {VERSION}")
|
||||
print(f"prev_version: {PREV_VERSION}")
|
||||
|
||||
notes_start_index = changelog_text.find(version_pattern) + len(version_pattern)
|
||||
notes_end_index = changelog_text.find(prev_version_pattern, notes_start_index) if PREV_VERSION and prev_version_pattern in changelog_text else len(changelog_text)
|
||||
|
||||
if new_content:
|
||||
return changelog_text[:notes_start_index] + f"{new_content}\n" + changelog_text[notes_end_index:]
|
||||
else:
|
||||
changeset_lines = changelog_text[notes_start_index:notes_end_index].split("\n")
|
||||
# Remove the first two lines from the regular changeset format, ex: \n### Patch Changes
|
||||
parsed_lines = "\n".join(changeset_lines[2:])
|
||||
updated_changelog = changelog_text[:notes_start_index] + parsed_lines + changelog_text[notes_end_index:]
|
||||
updated_changelog = updated_changelog.replace(f"## {VERSION}", f"## [{VERSION}]")
|
||||
return updated_changelog
|
||||
|
||||
with open(CHANGELOG_PATH, 'r') as f:
|
||||
changelog_content = f.read()
|
||||
|
||||
new_changelog = overwrite_changelog_section(changelog_content, NEW_CONTENT)
|
||||
print("----------------------------------------------------------------------------------")
|
||||
print(new_changelog)
|
||||
print("----------------------------------------------------------------------------------")
|
||||
# Write back to CHANGELOG.md
|
||||
with open(CHANGELOG_PATH, 'w') as f:
|
||||
f.write(new_changelog)
|
||||
|
||||
print(f"{CHANGELOG_PATH} updated successfully!")
|
||||
64
.github/scripts/parse_changeset_changelog.py
vendored
|
|
@ -1,64 +0,0 @@
|
|||
"""
|
||||
This script extracts the release notes section for a specific version from CHANGELOG.md.
|
||||
|
||||
The script:
|
||||
1. Takes a version number and changelog path as input from environment variables
|
||||
2. Finds the section in the changelog for the specified version
|
||||
3. Extracts the content between the current version header and the next version header
|
||||
(or end of file if it's the latest version)
|
||||
4. Outputs the extracted release notes to GITHUB_OUTPUT for use in creating GitHub releases
|
||||
|
||||
Environment Variables:
|
||||
GITHUB_OUTPUT: Path to GitHub Actions output file
|
||||
CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md')
|
||||
VERSION: The version number to extract notes for
|
||||
"""
|
||||
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT")
|
||||
CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md")
|
||||
VERSION = os.environ['VERSION']
|
||||
|
||||
def parse_changelog_section(content: str):
|
||||
"""Parse a specific version section from the changelog content.
|
||||
|
||||
Args:
|
||||
content: The full changelog content as a string
|
||||
|
||||
Returns:
|
||||
The formatted content for this version, or None if version not found
|
||||
|
||||
Example:
|
||||
>>> content = "## 1.2.0\\nChanges\\n## 1.1.0\\nOld changes"
|
||||
>>> parse_changelog_section(content)
|
||||
'Changes\\n'
|
||||
"""
|
||||
# Find the section for the specified version
|
||||
version_pattern = f"## {VERSION}\n"
|
||||
print(f"latest version: {VERSION}")
|
||||
notes_start_index = content.find(version_pattern) + len(version_pattern)
|
||||
prev_version = subprocess.getoutput("git show origin/main:package.json | grep '\"version\":' | cut -d'\"' -f4")
|
||||
print(f"prev_version: {prev_version}")
|
||||
prev_version_pattern = f"## {prev_version}\n"
|
||||
notes_end_index = content.find(prev_version_pattern, notes_start_index) if prev_version_pattern in content else len(content)
|
||||
|
||||
return content[notes_start_index:notes_end_index]
|
||||
|
||||
with open(CHANGELOG_PATH, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
formatted_content = parse_changelog_section(content)
|
||||
if not formatted_content:
|
||||
print(f"Version {VERSION} not found in changelog", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(formatted_content)
|
||||
|
||||
# Write the extracted release notes to GITHUB_OUTPUT
|
||||
with open(GITHUB_OUTPUT, "a") as gha_output:
|
||||
gha_output.write(f"release-notes<<EOF\n{formatted_content}\nEOF")
|
||||
125
.github/scripts/release-notes-prompt.py
vendored
|
|
@ -1,125 +0,0 @@
|
|||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import re
|
||||
import tiktoken # type: ignore
|
||||
from datetime import datetime;
|
||||
from pytz import timezone
|
||||
|
||||
GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT")
|
||||
BASE_REF = os.getenv("BASE_REF", "main")
|
||||
HEAD_SHA = os.environ["HEAD_SHA"]
|
||||
PR_TITLE = os.environ["PR_TITLE"]
|
||||
PR_BODY = os.environ["PR_BODY"]
|
||||
EXISTING_NOTES = os.environ.get("EXISTING_NOTES", "null")
|
||||
MODEL_NAME = os.environ.get('MODEL_NAME', 'gpt-3.5-turbo-16k')
|
||||
CUSTOM_PROMPT = os.environ.get('CUSTOM_PROMPT', '')
|
||||
|
||||
def extract_description_section(pr_body):
|
||||
# Find content between ## Description and the next ## or end of text
|
||||
description_match = re.search(r'## Description\s*\n(.*?)(?=\n##|$)', pr_body, re.DOTALL)
|
||||
if description_match:
|
||||
content = description_match.group(1).strip()
|
||||
# Remove the comment line if it exists
|
||||
comment_pattern = r'\[comment\]:.+?\n'
|
||||
content = re.sub(comment_pattern, '', content)
|
||||
return content.strip()
|
||||
return ""
|
||||
|
||||
def extract_ellipsis_important(pr_body):
|
||||
# Find content between <!-- ELLIPSIS_HIDDEN --> and <!-- ELLIPSIS_HIDDEN --> that contains [!IMPORTANT]
|
||||
ellipsis_match = re.search(r'<!--\s*ELLIPSIS_HIDDEN\s*-->(.*?)<!--\s*ELLIPSIS_HIDDEN\s*-->', pr_body, re.DOTALL)
|
||||
if ellipsis_match:
|
||||
content = ellipsis_match.group(1).strip()
|
||||
important_match = re.search(r'\[!IMPORTANT\](.*?)(?=\[!|$)', content, re.DOTALL)
|
||||
if important_match:
|
||||
important_text = important_match.group(1).strip()
|
||||
important_text = re.sub(r'^-+\s*', '', important_text)
|
||||
return important_text.strip()
|
||||
return ""
|
||||
|
||||
def extract_coderabbit_summary(pr_body):
|
||||
# Find content between ## Summary by CodeRabbit and the next ## or end of text
|
||||
summary_match = re.search(r'## Summary by CodeRabbit\s*\n(.*?)(?=\n##|$)', pr_body, re.DOTALL)
|
||||
return summary_match.group(1).strip() if summary_match else ""
|
||||
|
||||
def num_tokens_from_string(string: str, model_name: str) -> int:
|
||||
"""
|
||||
Calculate the number of tokens in a text string for a specific model.
|
||||
|
||||
Args:
|
||||
string: The input text to count tokens for
|
||||
model_name: Name of the OpenAI model to use for token counting
|
||||
|
||||
Returns:
|
||||
int: Number of tokens in the input string
|
||||
"""
|
||||
encoding = tiktoken.encoding_for_model(model_name)
|
||||
num_tokens = len(encoding.encode(string))
|
||||
return num_tokens
|
||||
|
||||
def truncate_to_token_limit(text, max_tokens, model_name):
|
||||
"""
|
||||
Truncate text to fit within a maximum token limit for a specific model.
|
||||
|
||||
Args:
|
||||
text: The input text to truncate
|
||||
max_tokens: Maximum number of tokens allowed
|
||||
model_name: Name of the OpenAI model to use for tokenization
|
||||
|
||||
Returns:
|
||||
str: Truncated text that fits within the token limit
|
||||
"""
|
||||
encoding = tiktoken.encoding_for_model(model_name)
|
||||
encoded = encoding.encode(text)
|
||||
truncated = encoded[:max_tokens]
|
||||
return encoding.decode(truncated)
|
||||
|
||||
# Extract sections and combine into PR_OVERVIEW
|
||||
description = extract_description_section(PR_BODY)
|
||||
important = extract_ellipsis_important(PR_BODY)
|
||||
summary = extract_coderabbit_summary(PR_BODY)
|
||||
|
||||
PR_OVERVIEW = "\n\n".join(filter(None, [description, important, summary]))
|
||||
|
||||
# Get git information
|
||||
base_sha = subprocess.getoutput(f"git rev-parse origin/{BASE_REF}") if BASE_REF == 'main' else BASE_REF
|
||||
diff_overview = subprocess.getoutput(f"git diff {base_sha}..{HEAD_SHA} --name-status | awk '{{print $2}}' | sort | uniq -c | awk '{{print $2 \": \" $1 \" files changed\"}}'")
|
||||
git_log = subprocess.getoutput(f"git log {base_sha}..{HEAD_SHA} --pretty=format:'%h - %s (%an)' --reverse | head -n 50")
|
||||
git_diff = subprocess.getoutput(f"git diff {base_sha}..{HEAD_SHA} --minimal --abbrev --ignore-cr-at-eol --ignore-space-at-eol --ignore-space-change --ignore-all-space --ignore-blank-lines --unified=0 --diff-filter=ACDMRT")
|
||||
|
||||
max_tokens = 14000 # Reserve some tokens for the response
|
||||
changes_summary = truncate_to_token_limit(diff_overview, 1000, MODEL_NAME)
|
||||
git_logs = truncate_to_token_limit(git_log, 2000, MODEL_NAME)
|
||||
changes_diff = truncate_to_token_limit(git_diff, max_tokens - num_tokens_from_string(changes_summary, MODEL_NAME) - num_tokens_from_string(git_logs, MODEL_NAME) - 1000, MODEL_NAME)
|
||||
|
||||
# Get today's existing changelog if any
|
||||
existing_changelog = EXISTING_NOTES if EXISTING_NOTES != "null" else None
|
||||
existing_changelog_text = f"\nAdditional context:\n{existing_changelog}" if existing_changelog else ""
|
||||
TODAY = datetime.now(timezone('US/Eastern')).isoformat(sep=' ', timespec='seconds')
|
||||
|
||||
BASE_PROMPT = CUSTOM_PROMPT if CUSTOM_PROMPT else f"""Based on the following 'PR Information', please generate concise and informative release notes to be read by developers.
|
||||
Format the release notes with markdown, and always use this structure: a descriptive and very short title (no more than 8 words) with heading level 2, a paragraph with a summary of changes (no header), and if applicable, sections for '🚀 New Features & Improvements', '🐛 Bugs Fixed' and '🔧 Other Updates', with heading level 3, skip respectively the sections if not applicable.
|
||||
Finally include the following markdown comment with the PR merged date: <!-- PR_DATE: {TODAY} -->.
|
||||
Avoid being repetitive and focus on the most important changes and their impact, discard any mention of version bumps/updates, changeset files, environment variables or syntax updates.
|
||||
PR Information:"""
|
||||
|
||||
OPENAI_PROMPT = f"""{BASE_PROMPT}
|
||||
Git log summary:
|
||||
{changes_summary}
|
||||
Commit Messages:
|
||||
{git_logs}
|
||||
PR Title:
|
||||
{PR_TITLE}
|
||||
PR Overview:
|
||||
{PR_OVERVIEW}{existing_changelog_text}
|
||||
Code Diff:
|
||||
{json.dumps(changes_diff)}"""
|
||||
|
||||
print("OpenAI Prompt")
|
||||
print("----------------------------------------------------------------")
|
||||
print(OPENAI_PROMPT)
|
||||
|
||||
# Write the prompt to GITHUB_OUTPUT
|
||||
with open(GITHUB_OUTPUT, "a") as outputs_file:
|
||||
outputs_file.write(f"OPENAI_PROMPT<<EOF\n{OPENAI_PROMPT}\nEOF")
|
||||
45
.github/workflows/build-vsix.yml
vendored
|
|
@ -1,45 +0,0 @@
|
|||
name: Build VSIX
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'build'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install all dependencies
|
||||
run: npm run install:all
|
||||
|
||||
- name: Build Extension
|
||||
run: npm run build
|
||||
|
||||
- name: Upload VSIX artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: extension-vsix
|
||||
path: bin/*.vsix
|
||||
|
||||
- name: Comment PR with artifact link
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Build successful! 🚀
|
||||
You can download the VSIX extension [here](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
||||
12
.github/workflows/changeset-release.yml
vendored
|
|
@ -10,6 +10,7 @@ env:
|
|||
REPO_PATH: ${{ github.repository }}
|
||||
GIT_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || 'main' }}
|
||||
NODE_VERSION: 20.18.1
|
||||
PNPM_VERSION: 10.8.1
|
||||
|
||||
jobs:
|
||||
# Job 1: Create version bump PR when changesets are merged to main
|
||||
|
|
@ -30,15 +31,18 @@ jobs:
|
|||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ env.GIT_REF }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm run install:all
|
||||
run: pnpm install
|
||||
|
||||
# Check if there are any new changesets to process
|
||||
- name: Check for changesets
|
||||
|
|
@ -56,7 +60,7 @@ jobs:
|
|||
with:
|
||||
commit: "changeset version bump"
|
||||
title: "Changeset version bump"
|
||||
version: npm run version-packages # This performs the changeset version bump
|
||||
version: pnpm --filter roo-cline version-packages # This performs the changeset version bump
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
|
|
|||
76
.github/workflows/code-qa.yml
vendored
|
|
@ -10,6 +10,7 @@ on:
|
|||
|
||||
env:
|
||||
NODE_VERSION: 20.18.1
|
||||
PNPM_VERSION: 10.8.1
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
|
|
@ -17,32 +18,38 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: npm run install:all
|
||||
- name: Compile
|
||||
run: npm run compile
|
||||
run: pnpm install
|
||||
- name: Check types
|
||||
run: npm run check-types
|
||||
run: pnpm check-types
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
run: pnpm lint
|
||||
|
||||
check-translations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: npm run install:all
|
||||
run: pnpm install
|
||||
- name: Verify all translations are complete
|
||||
run: node scripts/find-missing-translations.js
|
||||
|
||||
|
|
@ -51,15 +58,19 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: npm run install:all
|
||||
run: pnpm install
|
||||
- name: Run knip checks
|
||||
run: npm run knip
|
||||
run: pnpm knip
|
||||
|
||||
test-extension:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
|
@ -69,19 +80,20 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: npm run install:all
|
||||
- name: Compile (to build and copy WASM files)
|
||||
run: npm run compile
|
||||
- name: Run jest unit tests
|
||||
run: npx jest --silent
|
||||
- name: Run vitest unit tests
|
||||
run: npx vitest run --silent
|
||||
run: pnpm install
|
||||
- name: Run unit tests
|
||||
working-directory: src
|
||||
run: pnpm test
|
||||
|
||||
test-webview:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
|
@ -91,16 +103,20 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: npm run install:all
|
||||
run: pnpm install
|
||||
- name: Run unit tests
|
||||
working-directory: webview-ui
|
||||
run: npx jest --silent
|
||||
run: pnpm test
|
||||
|
||||
unit-test:
|
||||
needs: [test-extension, test-webview]
|
||||
|
|
@ -131,16 +147,20 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: npm run install:all
|
||||
run: pnpm install
|
||||
- name: Create .env.local file
|
||||
working-directory: e2e
|
||||
run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.local
|
||||
- name: Run integration tests
|
||||
working-directory: e2e
|
||||
run: xvfb-run -a npm run ci
|
||||
run: xvfb-run -a pnpm test:ci
|
||||
|
|
|
|||
11
.github/workflows/codeql.yml
vendored
|
|
@ -1,14 +1,3 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
|
|
|
|||
9
.github/workflows/marketplace-publish.yml
vendored
|
|
@ -29,20 +29,21 @@ jobs:
|
|||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
- name: Install Dependencies
|
||||
run: npm run install:all
|
||||
run: pnpm install
|
||||
- name: Create .env file
|
||||
run: echo "POSTHOG_API_KEY=${{ secrets.POSTHOG_API_KEY }}" >> .env
|
||||
- name: Package Extension
|
||||
run: |
|
||||
current_package_version=$(node -p "require('./package.json').version")
|
||||
npm run vsix
|
||||
pnpm vsix
|
||||
package=$(unzip -l bin/roo-cline-${current_package_version}.vsix)
|
||||
echo "$package" | grep -q "extension/package.json" || exit 1
|
||||
echo "$package" | grep -q "extension/package.nls.json" || exit 1
|
||||
echo "$package" | grep -q "extension/dist/extension.js" || exit 1
|
||||
echo "$package" | grep -q "extension/webview-ui/audio/celebration.wav" || exit 1
|
||||
echo "$package" | grep -q "extension/webview-ui/build/assets/index.js" || exit 1
|
||||
echo "$package" | grep -q "extension/node_modules/@vscode/codicons/dist/codicon.ttf" || exit 1
|
||||
echo "$package" | grep -q "extension/assets/codicons/codicon.ttf" || exit 1
|
||||
echo "$package" | grep -q "extension/assets/vscode-material-icons/icons/3d.svg" || exit 1
|
||||
echo "$package" | grep -q ".env" || exit 1
|
||||
- name: Create and Push Git Tag
|
||||
run: |
|
||||
|
|
@ -56,7 +57,7 @@ jobs:
|
|||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
run: |
|
||||
current_package_version=$(node -p "require('./package.json').version")
|
||||
npm run publish:marketplace
|
||||
pnpm publish:marketplace
|
||||
echo "Successfully published version $current_package_version to VS Code Marketplace"
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
|
|
|
|||
67
.github/workflows/nightly-publish.yml
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
name: Nightly Publish
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches: [main]
|
||||
workflow_run:
|
||||
workflows: ["Code QA Roo Code"]
|
||||
types:
|
||||
- completed
|
||||
branches: [main]
|
||||
workflow_dispatch: # Allows manual triggering.
|
||||
|
||||
env:
|
||||
NODE_VERSION: 20.18.1
|
||||
PNPM_VERSION: 10.8.1
|
||||
|
||||
jobs:
|
||||
publish-nightly:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
|
||||
permissions:
|
||||
contents: read # No tags pushed → read is enough.
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Count commits
|
||||
id: count
|
||||
run: echo "total=$(git rev-list --all --count)" >> $GITHUB_OUTPUT
|
||||
- name: Patch package.json version
|
||||
env:
|
||||
COMMIT_COUNT: ${{ steps.count.outputs.total }}
|
||||
run: |
|
||||
node <<'EOF'
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const pkgPath = path.join(__dirname, 'apps', 'vscode-nightly', 'package.nightly.json');
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath,'utf8'));
|
||||
const [maj, min] = pkg.version.split('.');
|
||||
pkg.version = `${maj}.${min}.${process.env.COMMIT_COUNT}`;
|
||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
||||
console.log(`🔖 Nightly version set to ${pkg.version}`);
|
||||
EOF
|
||||
- name: Build VSIX
|
||||
run: pnpm build:nightly # Produces bin/roo-code-nightly-0.0.[count].vsix
|
||||
- name: Publish to VS Code Marketplace
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
run: npx vsce publish --packagePath "bin/$(/bin/ls bin | head -n1)"
|
||||
- name: Publish to Open VSX Registry
|
||||
env:
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
run: npx ovsx publish "bin/$(ls bin | head -n1)"
|
||||
23
.github/workflows/update-contributors.yml
vendored
|
|
@ -6,6 +6,10 @@ on:
|
|||
- main
|
||||
workflow_dispatch: # Allows manual triggering
|
||||
|
||||
env:
|
||||
NODE_VERSION: 20.18.1
|
||||
PNPM_VERSION: 10.8.1
|
||||
|
||||
jobs:
|
||||
update-contributors:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -15,30 +19,29 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'pnpm'
|
||||
- name: Disable Husky
|
||||
run: |
|
||||
echo "HUSKY=0" >> $GITHUB_ENV
|
||||
git config --global core.hooksPath /dev/null
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
run: pnpm install
|
||||
- name: Update contributors and format
|
||||
run: |
|
||||
npm run update-contributors
|
||||
pnpm update-contributors
|
||||
npx prettier --write README.md
|
||||
if git diff --quiet; then echo "changes=false" >> $GITHUB_OUTPUT; else echo "changes=true" >> $GITHUB_OUTPUT; fi
|
||||
id: check-changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.check-changes.outputs.changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
|
|
@ -51,6 +54,6 @@ jobs:
|
|||
title: "Update contributors list"
|
||||
body: |
|
||||
Automated update of contributors list and related files
|
||||
|
||||
|
||||
This PR was created automatically by a GitHub Action workflow and includes all changed files.
|
||||
base: main
|
||||
|
|
|
|||
7
.gitignore
vendored
|
|
@ -31,15 +31,18 @@ docs/_site/
|
|||
!.env.*.sample
|
||||
|
||||
|
||||
#Local lint config
|
||||
# Local lint config
|
||||
.eslintrc.local.json
|
||||
|
||||
#Logging
|
||||
# Logging
|
||||
logs
|
||||
|
||||
# Vite development
|
||||
.vite-port
|
||||
|
||||
# Turborepo
|
||||
.turbo
|
||||
|
||||
# IntelliJ and Qodo plugin folders
|
||||
.idea/
|
||||
.qodo/
|
||||
|
|
|
|||
|
|
@ -5,19 +5,26 @@ if [ "$branch" = "main" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Detect if running on Windows and use npx.cmd, otherwise use npx
|
||||
# Detect if running on Windows and use pnpm.cmd, otherwise use pnpm.
|
||||
if [ "$OS" = "Windows_NT" ]; then
|
||||
pnpm_cmd="pnpm.cmd"
|
||||
else
|
||||
pnpm_cmd="pnpm"
|
||||
fi
|
||||
|
||||
"$pnpm_cmd" --filter roo-cline generate-types
|
||||
|
||||
if [ -n "$(git diff --name-only src/exports/roo-code.d.ts)" ]; then
|
||||
echo "Error: There are unstaged changes to roo-code.d.ts after running 'pnpm --filter roo-cline generate-types'."
|
||||
echo "Please review and stage the changes before committing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect if running on Windows and use npx.cmd, otherwise use npx.
|
||||
if [ "$OS" = "Windows_NT" ]; then
|
||||
npx_cmd="npx.cmd"
|
||||
else
|
||||
npx_cmd="npx"
|
||||
fi
|
||||
|
||||
npm run generate-types
|
||||
|
||||
if [ -n "$(git diff --name-only src/exports/roo-code.d.ts)" ]; then
|
||||
echo "Error: There are unstaged changes to roo-code.d.ts after running 'npm run generate-types'."
|
||||
echo "Please review and stage the changes before committing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$npx_cmd" lint-staged
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ if [ "$branch" = "main" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Detect if running on Windows and use npm.cmd, otherwise use npm
|
||||
# Detect if running on Windows and use pnpm.cmd, otherwise use pnpm.
|
||||
if [ "$OS" = "Windows_NT" ]; then
|
||||
npm_cmd="npm.cmd"
|
||||
pnpm_cmd="pnpm.cmd"
|
||||
else
|
||||
npm_cmd="npm"
|
||||
pnpm_cmd="pnpm"
|
||||
fi
|
||||
|
||||
"$npm_cmd" run compile
|
||||
"$pnpm_cmd" run lint check-types
|
||||
|
||||
# Check for new changesets.
|
||||
NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ')
|
||||
|
|
@ -20,6 +20,6 @@ echo "Changeset files: $NEW_CHANGESETS"
|
|||
|
||||
if [ "$NEW_CHANGESETS" == "0" ]; then
|
||||
echo "-------------------------------------------------------------------------------------"
|
||||
echo "Changes detected. Please run 'npm run changeset' to create a changeset if applicable."
|
||||
echo "Changes detected. Please run 'pnpm changeset' to create a changeset if applicable."
|
||||
echo "-------------------------------------------------------------------------------------"
|
||||
fi
|
||||
|
|
|
|||
1
.npmrc
|
|
@ -1 +0,0 @@
|
|||
registry=https://registry.npmjs.org/
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
dist/
|
||||
node_modules
|
||||
webview-ui/build/
|
||||
CHANGELOG.md
|
||||
package-lock.json
|
||||
4
.vscode/launch.json
vendored
|
|
@ -10,9 +10,9 @@
|
|||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}/src"],
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"outFiles": ["${workspaceFolder}/src/dist/**/*.js"],
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
|
|
|
|||
26
.vscode/tasks.json
vendored
|
|
@ -5,7 +5,7 @@
|
|||
"tasks": [
|
||||
{
|
||||
"label": "watch",
|
||||
"dependsOn": ["npm: dev", "npm: watch:tsc", "npm: watch:esbuild"],
|
||||
"dependsOn": ["webview", "watch:tsc", "watch:esbuild"],
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
|
|
@ -15,9 +15,9 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"label": "npm: dev",
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
"label": "webview",
|
||||
"type": "shell",
|
||||
"command": "pnpm --filter @roo-code/vscode-webview dev",
|
||||
"group": "build",
|
||||
"problemMatcher": {
|
||||
"owner": "vite",
|
||||
|
|
@ -32,14 +32,14 @@
|
|||
},
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"group": "webview-ui",
|
||||
"group": "watch",
|
||||
"reveal": "always"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "npm: watch:esbuild",
|
||||
"type": "npm",
|
||||
"script": "watch:esbuild",
|
||||
"label": "watch:esbuild",
|
||||
"type": "shell",
|
||||
"command": "pnpm --filter roo-cline watch:esbuild",
|
||||
"group": "build",
|
||||
"problemMatcher": {
|
||||
"owner": "esbuild",
|
||||
|
|
@ -48,8 +48,8 @@
|
|||
},
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "\\[watch\\] build started",
|
||||
"endsPattern": "\\[watch\\] build finished"
|
||||
"beginsPattern": "esbuild-problem-matcher#onStart",
|
||||
"endsPattern": "esbuild-problem-matcher#onEnd"
|
||||
}
|
||||
},
|
||||
"isBackground": true,
|
||||
|
|
@ -59,9 +59,9 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"label": "npm: watch:tsc",
|
||||
"type": "npm",
|
||||
"script": "watch:tsc",
|
||||
"label": "watch:tsc",
|
||||
"type": "shell",
|
||||
"command": "pnpm --filter roo-cline watch:tsc",
|
||||
"group": "build",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
# Default
|
||||
.changeset/**
|
||||
.github/**
|
||||
.husky/**
|
||||
.vscode/**
|
||||
coverage/**
|
||||
node_modules/**
|
||||
src/**
|
||||
scripts/**
|
||||
.gitignore
|
||||
esbuild.js
|
||||
jest.*
|
||||
**/tsconfig.json
|
||||
**/.eslintrc.json
|
||||
.prettierignore
|
||||
**/*.map
|
||||
**/*.ts
|
||||
**/.gitignore
|
||||
|
||||
# Custom
|
||||
.env.sample
|
||||
.git-blame-ignore-revs
|
||||
.gitconfig
|
||||
.gitattributes
|
||||
.tool-versions
|
||||
.vite-port
|
||||
.nvmrc
|
||||
.clinerules*
|
||||
.roomodes
|
||||
.rooignore
|
||||
.roo/**
|
||||
benchmark/**
|
||||
docs/**
|
||||
e2e/**
|
||||
evals/**
|
||||
locales/**
|
||||
out/**
|
||||
ellipsis.yaml
|
||||
knip.json
|
||||
|
||||
# Ignore all webview-ui files except the build directory.
|
||||
# https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore
|
||||
webview-ui/src/**
|
||||
webview-ui/public/**
|
||||
webview-ui/scripts/**
|
||||
webview-ui/index.html
|
||||
webview-ui/README.md
|
||||
webview-ui/package.json
|
||||
webview-ui/package-lock.json
|
||||
webview-ui/node_modules/**
|
||||
|
||||
# Include codicons
|
||||
!node_modules/@vscode/codicons/dist/codicon.css
|
||||
!node_modules/@vscode/codicons/dist/codicon.ttf
|
||||
|
||||
# Include material icons
|
||||
!node_modules/vscode-material-icons/generated/**
|
||||
|
||||
# Include default themes JSON files used in getTheme
|
||||
!src/integrations/theme/default-themes/**
|
||||
|
||||
# Ignore doc assets
|
||||
assets/docs/**
|
||||
|
||||
# Include icons and images
|
||||
!assets/icons/**
|
||||
!assets/images/**
|
||||
|
||||
# Include .env file for telemetry
|
||||
!.env
|
||||
|
||||
# Ignore IntelliJ and Qodo plugin folders
|
||||
.idea/**
|
||||
.qodo/**
|
||||
|
|
@ -92,7 +92,7 @@ git clone https://github.com/YOUR_USERNAME/Roo-Code.git
|
|||
2. **Install Dependencies:**
|
||||
|
||||
```
|
||||
npm run install:all
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. **Debugging:** Open with VS Code (`F5`).
|
||||
|
|
|
|||
29
MONOREPO.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Monorepo Guide
|
||||
|
||||
Roo Code has transitioned to a monorepo powered by [PNPM workspaces](https://pnpm.io/workspaces) and [Turborepo](https://turborepo.com).
|
||||
|
||||
When you first pull down the monorepo changes from git you'll need to re-install all packages using pnpm. You can install pnpm using [these](https://pnpm.io/installation) instructions. If you're on MacOS the easiest option is to use Homebrew:
|
||||
|
||||
```sh
|
||||
brew install pnpm
|
||||
```
|
||||
|
||||
Once pnpm is installed you should wipe out your existing node_modules directories for a fresh start:
|
||||
|
||||
```sh
|
||||
# This is optional, but recommended.
|
||||
find . -name node_modules | xargs rm -rvf
|
||||
```
|
||||
|
||||
And then install your packages:
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
If things are in good working order then you should be able to build a vsix and install it in VSCode:
|
||||
|
||||
```sh
|
||||
pnpm build --out ../bin/roo-code-main.vsix && \
|
||||
code --install-extension bin/roo-code-main.vsix
|
||||
```
|
||||
13
README.md
|
|
@ -133,24 +133,19 @@ git clone https://github.com/RooCodeInc/Roo-Code.git
|
|||
2. **Install dependencies**:
|
||||
|
||||
```sh
|
||||
npm run install:all
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. **Start the webview (Vite/React app with HMR)**:
|
||||
3. **Run the extension**:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
4. **Debug**:
|
||||
Press `F5` (or **Run** → **Start Debugging**) in VSCode to open a new session with Roo Code loaded.
|
||||
Press `F5` (or **Run** → **Start Debugging**) in VSCode to open a new window with Roo Code running.
|
||||
|
||||
Changes to the webview will appear immediately. Changes to the core extension will require a restart of the extension host.
|
||||
|
||||
Alternatively you can build a .vsix and install it directly in VSCode:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
pnpm build
|
||||
```
|
||||
|
||||
A `.vsix` file will appear in the `bin/` directory which can be installed with:
|
||||
|
|
|
|||
25
apps/vscode-nightly/.eslintrc.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{
|
||||
"selector": "import",
|
||||
"format": ["camelCase", "PascalCase"]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }],
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off"
|
||||
},
|
||||
"ignorePatterns": ["dist"]
|
||||
}
|
||||
1
apps/vscode-nightly/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
build
|
||||
163
apps/vscode-nightly/esbuild.mjs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import * as esbuild from "esbuild"
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
import { getGitSha, copyPaths, copyLocales, copyWasms, generatePackageJson } from "@roo-code/build"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
async function main() {
|
||||
const production = process.argv.includes("--production")
|
||||
const minify = production
|
||||
const sourcemap = !production
|
||||
|
||||
const overrideJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.nightly.json"), "utf8"))
|
||||
console.log(`[main] name: ${overrideJson.name}`)
|
||||
console.log(`[main] version: ${overrideJson.version}`)
|
||||
|
||||
const gitSha = getGitSha()
|
||||
console.log(`[main] gitSha: ${gitSha}`)
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').BuildOptions}
|
||||
*/
|
||||
const buildOptions = {
|
||||
bundle: true,
|
||||
minify,
|
||||
sourcemap,
|
||||
logLevel: "silent",
|
||||
format: "cjs",
|
||||
sourcesContent: false,
|
||||
platform: "node",
|
||||
define: {
|
||||
"process.env.PKG_NAME": '"roo-code-nightly"',
|
||||
"process.env.PKG_VERSION": `"${overrideJson.version}"`,
|
||||
"process.env.PKG_OUTPUT_CHANNEL": '"Roo-Code-Nightly"',
|
||||
...(gitSha ? { "process.env.PKG_SHA": `"${gitSha}"` } : {}),
|
||||
},
|
||||
}
|
||||
|
||||
const srcDir = path.join(__dirname, "..", "..", "src")
|
||||
const buildDir = path.join(__dirname, "build")
|
||||
const distDir = path.join(buildDir, "dist")
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').Plugin[]}
|
||||
*/
|
||||
const plugins = [
|
||||
{
|
||||
name: "copy-files",
|
||||
setup(build) {
|
||||
build.onEnd(() => {
|
||||
copyPaths(
|
||||
[
|
||||
["../README.md", "README.md"],
|
||||
["../CHANGELOG.md", "CHANGELOG.md"],
|
||||
["../LICENSE", "LICENSE"],
|
||||
[".vscodeignore", ".vscodeignore"],
|
||||
["assets", "assets"],
|
||||
["integrations", "integrations"],
|
||||
["node_modules/vscode-material-icons/generated", "assets/vscode-material-icons"],
|
||||
["../webview-ui/audio", "webview-ui/audio"],
|
||||
],
|
||||
srcDir,
|
||||
buildDir,
|
||||
)
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate-package-json",
|
||||
setup(build) {
|
||||
build.onEnd(() => {
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(srcDir, "package.json"), "utf8"))
|
||||
|
||||
const generatedPackageJson = generatePackageJson({
|
||||
packageJson,
|
||||
overrideJson,
|
||||
substitution: ["roo-cline", "roo-code-nightly"],
|
||||
})
|
||||
|
||||
fs.writeFileSync(path.join(buildDir, "package.json"), JSON.stringify(generatedPackageJson, null, 2))
|
||||
console.log(`[generate-package-json] Generated package.json`)
|
||||
|
||||
let count = 0
|
||||
|
||||
fs.readdirSync(path.join(srcDir)).forEach((file) => {
|
||||
if (file.startsWith("package.nls")) {
|
||||
fs.copyFileSync(path.join(srcDir, file), path.join(buildDir, file))
|
||||
count++
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`[copy-src] Copied ${count} package.nls*.json files to ${buildDir}`)
|
||||
|
||||
const nlsPkg = JSON.parse(fs.readFileSync(path.join(srcDir, "package.nls.json"), "utf8"))
|
||||
|
||||
const nlsNightlyPkg = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "package.nls.nightly.json"), "utf8"),
|
||||
)
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(buildDir, "package.nls.json"),
|
||||
JSON.stringify({ ...nlsPkg, ...nlsNightlyPkg }, null, 2),
|
||||
)
|
||||
|
||||
console.log(`[copy-src] Generated package.nls.json`)
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "copy-wasms",
|
||||
setup(build) {
|
||||
build.onEnd(() => copyWasms(srcDir, distDir))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "copy-locales",
|
||||
setup(build) {
|
||||
build.onEnd(() => copyLocales(srcDir, distDir))
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').BuildOptions}
|
||||
*/
|
||||
const extensionBuildOptions = {
|
||||
...buildOptions,
|
||||
plugins,
|
||||
entryPoints: [path.join(srcDir, "extension.ts")],
|
||||
outfile: path.join(distDir, "extension.js"),
|
||||
external: ["vscode"],
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').BuildOptions}
|
||||
*/
|
||||
const workerBuildOptions = {
|
||||
...buildOptions,
|
||||
entryPoints: [path.join(srcDir, "workers", "countTokens.ts")],
|
||||
outdir: path.join(distDir, "workers"),
|
||||
}
|
||||
|
||||
const [extensionBuildContext, workerBuildContext] = await Promise.all([
|
||||
esbuild.context(extensionBuildOptions),
|
||||
esbuild.context(workerBuildOptions),
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
extensionBuildContext.rebuild(),
|
||||
extensionBuildContext.dispose(),
|
||||
|
||||
workerBuildContext.rebuild(),
|
||||
workerBuildContext.dispose(),
|
||||
])
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
15
apps/vscode-nightly/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "@roo-code/vscode-nightly",
|
||||
"description": "Nightly build for the Roo Code VSCode extension.",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.8.1",
|
||||
"scripts": {
|
||||
"bundle": "pnpm clean && pnpm --filter @roo-code/build build && node esbuild.mjs",
|
||||
"build": "pnpm bundle --production && pnpm --filter @roo-code/vscode-webview build --mode nightly",
|
||||
"vsix": "pnpm build && cd build && mkdirp ../../../bin && npx vsce package --no-dependencies --out ../../../bin",
|
||||
"clean": "rimraf build .turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@roo-code/build": "workspace:^"
|
||||
}
|
||||
}
|
||||
6
apps/vscode-nightly/package.nightly.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "roo-code-nightly",
|
||||
"version": "0.0.1",
|
||||
"icon": "assets/icons/icon-nightly.png",
|
||||
"scripts": {}
|
||||
}
|
||||
7
apps/vscode-nightly/package.nls.nightly.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extension.displayName": "Roo Code Nightly",
|
||||
"views.contextMenu.label": "Roo Code Nightly",
|
||||
"views.terminalMenu.label": "Roo Code Nightly",
|
||||
"views.activitybar.title": "Roo Code Nightly",
|
||||
"configuration.title": "Roo Code Nightly"
|
||||
}
|
||||
|
|
@ -1,621 +0,0 @@
|
|||
# Cache Strategy Documentation
|
||||
|
||||
This document provides an overview of the cache strategy implementation for Amazon Bedrock in the Roo-Code project, including class relationships and sequence diagrams.
|
||||
|
||||
## Class Relationship Diagram
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class CacheStrategy {
|
||||
<<abstract>>
|
||||
#config: CacheStrategyConfig
|
||||
#systemTokenCount: number
|
||||
+determineOptimalCachePoints(): CacheResult
|
||||
#initializeMessageGroups(): void
|
||||
#calculateSystemTokens(): void
|
||||
#createCachePoint(): ContentBlock
|
||||
#messagesToContentBlocks(messages): Message[]
|
||||
#meetsMinTokenThreshold(tokenCount): boolean
|
||||
#estimateTokenCount(message): number
|
||||
#applyCachePoints(messages, placements): Message[]
|
||||
#formatResult(systemBlocks, messages): CacheResult
|
||||
}
|
||||
|
||||
class MultiPointStrategy {
|
||||
+determineOptimalCachePoints(): CacheResult
|
||||
-determineMessageCachePoints(minTokensPerPoint, remainingCachePoints): CachePointPlacement[]
|
||||
-formatWithoutCachePoints(): CacheResult
|
||||
-findOptimalPlacementForRange(startIndex, endIndex, minTokensPerPoint): CachePointPlacement
|
||||
}
|
||||
|
||||
class AwsBedrockHandler {
|
||||
-client: BedrockRuntimeClient
|
||||
-costModelConfig: object
|
||||
-previousCachePointPlacements: Map<string, CachePointPlacement[]>
|
||||
+createMessage(systemPrompt, messages): ApiStream
|
||||
+completePrompt(prompt): Promise<string>
|
||||
-supportsAwsPromptCache(modelConfig): boolean
|
||||
-getModelByName(modelName): object
|
||||
+getModel(): object
|
||||
-removeCachePoints(content): any
|
||||
-convertToBedrockConverseMessages(anthropicMessages, systemMessage, usePromptCache, modelInfo, conversationId): object
|
||||
}
|
||||
|
||||
class CacheStrategyConfig {
|
||||
+modelInfo: ModelInfo
|
||||
+systemPrompt?: string
|
||||
+messages: MessageParam[]
|
||||
+usePromptCache: boolean
|
||||
+previousCachePointPlacements?: CachePointPlacement[]
|
||||
}
|
||||
|
||||
class ModelInfo {
|
||||
+maxTokens: number
|
||||
+contextWindow: number
|
||||
+supportsPromptCache: boolean
|
||||
+maxCachePoints: number
|
||||
+minTokensPerCachePoint: number
|
||||
+cachableFields: Array<string>
|
||||
}
|
||||
|
||||
class CacheResult {
|
||||
+system: SystemContentBlock[]
|
||||
+messages: Message[]
|
||||
+messageCachePointPlacements?: CachePointPlacement[]
|
||||
}
|
||||
|
||||
class CachePointPlacement {
|
||||
+index: number
|
||||
+type: string
|
||||
+tokensCovered: number
|
||||
}
|
||||
|
||||
CacheStrategy <|-- MultiPointStrategy : extends
|
||||
CacheStrategy o-- CacheStrategyConfig : uses
|
||||
CacheStrategyConfig o-- ModelInfo : contains
|
||||
CacheStrategy ..> CacheResult : produces
|
||||
CacheStrategy ..> CachePointPlacement : creates
|
||||
AwsBedrockHandler ..> MultiPointStrategy : creates
|
||||
AwsBedrockHandler ..> CachePointPlacement : tracks
|
||||
MultiPointStrategy ..> CachePointPlacement : preserves
|
||||
```
|
||||
|
||||
## Sequence Diagram: Multi-Point Strategy
|
||||
|
||||
This diagram illustrates the process flow when using the MultiPointStrategy with multiple cache points in messages.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as Client Code
|
||||
participant Bedrock as AwsBedrockHandler
|
||||
participant Strategy as MultiPointStrategy
|
||||
participant AWS as Amazon Bedrock Service
|
||||
|
||||
Client->>Bedrock: createMessage(systemPrompt, messages)
|
||||
Note over Bedrock: Generate conversationId to track cache points
|
||||
Bedrock->>Bedrock: getModel() to get model info
|
||||
Bedrock->>Bedrock: Check if model supports prompt cache
|
||||
|
||||
Bedrock->>Strategy: new MultiPointStrategy(config)
|
||||
Note over Strategy: config contains modelInfo, systemPrompt, messages, usePromptCache, previousCachePointPlacements
|
||||
|
||||
Bedrock->>Strategy: determineOptimalCachePoints()
|
||||
|
||||
alt usePromptCache is false or no messages
|
||||
Strategy->>Strategy: formatWithoutCachePoints()
|
||||
else
|
||||
Strategy->>Strategy: Check if system cache is supported
|
||||
alt supportsSystemCache and systemPrompt exists
|
||||
Strategy->>Strategy: meetsMinTokenThreshold(systemTokenCount)
|
||||
alt systemTokenCount >= minTokensPerCachePoint
|
||||
Strategy->>Strategy: Add cache point after system prompt
|
||||
Note over Strategy: Decrement remainingCachePoints
|
||||
end
|
||||
end
|
||||
|
||||
Strategy->>Strategy: determineMessageCachePoints(minTokensPerPoint, remainingCachePoints)
|
||||
alt previousCachePointPlacements exists
|
||||
Note over Strategy: Analyze previous placements
|
||||
Note over Strategy: Preserve N-1 cache points when possible
|
||||
Note over Strategy: Determine which points to keep or combine
|
||||
else
|
||||
loop while currentIndex < messages.length and remainingCachePoints > 0
|
||||
Strategy->>Strategy: findOptimalPlacementForRange(currentIndex, totalMessages-1, minTokensPerPoint)
|
||||
alt placement found
|
||||
Strategy->>Strategy: Add placement to placements array
|
||||
Strategy->>Strategy: Update currentIndex and decrement remainingCachePoints
|
||||
end
|
||||
end
|
||||
end
|
||||
Strategy->>Strategy: applyCachePoints(messages, placements)
|
||||
Strategy->>Strategy: Store cache point placements in result
|
||||
end
|
||||
|
||||
Strategy-->>Bedrock: Return CacheResult with system blocks, messages, and messageCachePointPlacements
|
||||
|
||||
Bedrock->>Bedrock: Store cache point placements for conversationId
|
||||
Bedrock->>AWS: Send request with multiple cache points
|
||||
AWS-->>Bedrock: Stream response
|
||||
Bedrock-->>Client: Yield response chunks
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Cache Strategy
|
||||
|
||||
The cache strategy system is designed to optimize the placement of cache points in Amazon Bedrock API requests. Cache points allow the service to reuse previously processed parts of the prompt, reducing token usage and improving response times.
|
||||
|
||||
- **MultiPointStrategy**: Upon first MR of Bedrock caching, this strategy is used for all cache point placement scenarios. It distributes cache points throughout the conversation to maximize caching efficiency, whether the model supports one or multiple cache points.
|
||||
|
||||
### MultiPointStrategy Placement Logic
|
||||
|
||||
- **System Prompt Caching**: If the system prompt is large enough (exceeds minTokensPerCachePoint), a cache point is placed after it.
|
||||
|
||||
- **Message Caching**: The strategy uses a simplified approach for placing cache points in messages:
|
||||
|
||||
1. For new conversations (no previous cache points):
|
||||
|
||||
- It iteratively finds the last user message in each range
|
||||
- It ensures each placement covers at least the minimum token threshold
|
||||
- It continues until all available cache points are used or no more valid placements can be found
|
||||
|
||||
2. For growing conversations (with previous cache points):
|
||||
- It preserves previous cache points when possible
|
||||
- It analyzes the token distribution between existing cache points
|
||||
- It compares the token count of new messages with the smallest gap between existing cache points
|
||||
- It only combines cache points if the new messages have more tokens than the smallest gap
|
||||
|
||||
A key challenge in cache point placement is maintaining consistency across consecutive messages in a growing conversation. When new messages are added to a conversation, we want to ensure that:
|
||||
|
||||
1. Cache points from previous messages are reused as much as possible to maximize cache hits
|
||||
2. New cache points are placed optimally for the new messages
|
||||
|
||||
The simplified approach ensures that:
|
||||
|
||||
- Cache points are always placed after user messages, which are natural conversation boundaries
|
||||
- Each cache point covers at least the minimum token threshold
|
||||
- N-1 cache points remain in the same location when possible in growing conversations
|
||||
- Cache points are combined only when it makes sense to do so (when the benefit outweighs the cost)
|
||||
- New messages receive cache points only when they contain enough tokens to justify the reallocation
|
||||
|
||||
The examples in this document reflect this optimized implementation.
|
||||
|
||||
### Integration with Amazon Bedrock
|
||||
|
||||
The AwsBedrockHandler class integrates with the cache strategies by:
|
||||
|
||||
1. Determining if the model supports prompt caching
|
||||
2. Creating the appropriate strategy based on model capabilities
|
||||
3. Applying the strategy to format messages with optimal cache points
|
||||
4. Sending the formatted request to Amazon Bedrock
|
||||
5. Processing and returning the response
|
||||
|
||||
## Usage Considerations
|
||||
|
||||
- Cache points are only effective if the same content is reused across multiple requests
|
||||
- The minimum token threshold ensures cache points are only placed where they provide meaningful benefits
|
||||
- System prompt caching is prioritized when available, as it's typically static across requests
|
||||
- Message caching is more complex and depends on conversation structure and token distribution
|
||||
|
||||
## Examples: Multi-Point Strategy Cache Point Placement
|
||||
|
||||
### Example 1: Initial Cache Point Placement
|
||||
|
||||
In this example, we'll demonstrate how the `determineMessageCachePoints` method places cache points in a new conversation.
|
||||
|
||||
**Input Configuration:**
|
||||
|
||||
```javascript
|
||||
const config = {
|
||||
modelInfo: {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 200000,
|
||||
supportsPromptCache: true,
|
||||
maxCachePoints: 3,
|
||||
minTokensPerCachePoint: 100,
|
||||
cachableFields: ["system", "messages"],
|
||||
},
|
||||
systemPrompt: "You are a helpful assistant.", // ~10 tokens
|
||||
messages: [
|
||||
{ role: "user", content: "Tell me about machine learning." }, // ~50 tokens
|
||||
{ role: "assistant", content: "Machine learning is a field of study..." }, // ~150 tokens
|
||||
{ role: "user", content: "What about deep learning?" }, // ~40 tokens
|
||||
{ role: "assistant", content: "Deep learning is a subset of machine learning..." }, // ~160 tokens
|
||||
],
|
||||
usePromptCache: true,
|
||||
}
|
||||
```
|
||||
|
||||
**Execution Process:**
|
||||
|
||||
1. First, the system prompt is evaluated for caching (10 tokens < minTokensPerCachePoint of 100), so no cache point is used there.
|
||||
2. The `determineMessageCachePoints` method is called with `minTokensPerPoint = 100` and `remainingCachePoints = 3`.
|
||||
3. Since there are no previous cache point placements, it enters the special case for new conversations.
|
||||
4. It calls `findOptimalPlacementForRange` with the entire message range.
|
||||
5. The method finds the last user message in the range (index 2: "What about deep learning?").
|
||||
6. It calculates the total tokens covered (240) and verifies it exceeds the minimum threshold (100).
|
||||
7. It adds this placement to the placements array and continues the process for the next range.
|
||||
8. Since there are no more user messages after this point that would cover enough tokens, no more cache points are placed.
|
||||
|
||||
**Output Cache Point Placements:**
|
||||
|
||||
```javascript
|
||||
;[
|
||||
{
|
||||
index: 2, // After the second user message (What about deep learning?)
|
||||
type: "message",
|
||||
tokensCovered: 240, // ~240 tokens covered (first 3 messages)
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
**Resulting Message Structure:**
|
||||
|
||||
```
|
||||
[User]: Tell me about machine learning.
|
||||
[Assistant]: Machine learning is a field of study...
|
||||
[User]: What about deep learning?
|
||||
[CACHE POINT]
|
||||
[Assistant]: Deep learning is a subset of machine learning...
|
||||
```
|
||||
|
||||
**Note**: The algorithm places a cache point after the second user message (the last user message in the range) because it's the optimal placement and the accumulated tokens (240) exceed the minimum threshold (100).
|
||||
|
||||
### Example 2: Adding One Exchange with Cache Point Preservation
|
||||
|
||||
Now, let's see what happens when we add one more exchange (user-assistant pair) to the conversation and use the cache point preservation logic:
|
||||
|
||||
**Updated Input Configuration with Previous Cache Points:**
|
||||
|
||||
```javascript
|
||||
const config = {
|
||||
// Same modelInfo and systemPrompt as before
|
||||
messages: [
|
||||
// Previous 4 messages...
|
||||
{ role: "user", content: "How do neural networks work?" }, // ~50 tokens
|
||||
{ role: "assistant", content: "Neural networks are composed of layers of nodes..." }, // ~180 tokens
|
||||
],
|
||||
usePromptCache: true,
|
||||
// Pass the previous cache point placements from Example 1
|
||||
previousCachePointPlacements: [
|
||||
{
|
||||
index: 2, // After the second user message (What about deep learning?)
|
||||
type: "message",
|
||||
tokensCovered: 240,
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**Execution Process for Example 2 with Cache Point Preservation:**
|
||||
|
||||
1. The system prompt evaluation remains the same (no cache point used).
|
||||
2. The algorithm detects that `previousCachePointPlacements` is provided in the config.
|
||||
3. It analyzes the previous cache point placements and the current message structure.
|
||||
4. Since we have 3 total cache points available and used 1 in the previous conversation, we can preserve the previous cache point and still have 2 remaining for the new messages.
|
||||
5. The algorithm preserves the cache point from the previous conversation:
|
||||
- The cache point at index 2 (after "What about deep learning?")
|
||||
6. It then calculates the optimal placement for the remaining cache points based on the new messages.
|
||||
7. Since there are 2 new messages with significant token count (230 tokens), it places a second cache point after the new user message.
|
||||
|
||||
**Output Cache Point Placements with Preservation:**
|
||||
|
||||
```javascript
|
||||
;[
|
||||
{
|
||||
index: 2, // After the second user message (What about deep learning?) - PRESERVED
|
||||
type: "message",
|
||||
tokensCovered: 240, // ~240 tokens covered (first 3 messages)
|
||||
},
|
||||
{
|
||||
index: 4, // After the third user message (How do neural networks work?) - NEW PLACEMENT
|
||||
type: "message",
|
||||
tokensCovered: 230, // ~230 tokens covered (messages between cache points)
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
**Resulting Message Structure with Preservation:**
|
||||
|
||||
```
|
||||
[User]: Tell me about machine learning.
|
||||
[Assistant]: Machine learning is a field of study...
|
||||
[User]: What about deep learning?
|
||||
[CACHE POINT 1] - PRESERVED FROM PREVIOUS
|
||||
[Assistant]: Deep learning is a subset of machine learning...
|
||||
[User]: How do neural networks work?
|
||||
[CACHE POINT 2] - NEW PLACEMENT
|
||||
[Assistant]: Neural networks are composed of layers of nodes...
|
||||
```
|
||||
|
||||
**Note**: The algorithm preserved the cache point from the previous conversation and placed a new cache point for the new messages. This ensures maximum cache hit rates while still adapting to the growing conversation.
|
||||
|
||||
### Example 3: Adding Another Exchange with Cache Point Preservation
|
||||
|
||||
Let's add one more exchange to see how the cache strategy continues to adapt:
|
||||
|
||||
**Updated Input Configuration with Previous Cache Points:**
|
||||
|
||||
```javascript
|
||||
const config = {
|
||||
// Same modelInfo and systemPrompt as before
|
||||
messages: [
|
||||
// Previous 6 messages...
|
||||
{ role: "user", content: "Can you explain backpropagation?" }, // ~40 tokens
|
||||
{ role: "assistant", content: "Backpropagation is an algorithm used to train neural networks..." }, // ~170 tokens
|
||||
],
|
||||
usePromptCache: true,
|
||||
// Pass the previous cache point placements from Example 2
|
||||
previousCachePointPlacements: [
|
||||
{
|
||||
index: 2, // After the second user message (What about deep learning?)
|
||||
type: "message",
|
||||
tokensCovered: 240,
|
||||
},
|
||||
{
|
||||
index: 4, // After the third user message (How do neural networks work?)
|
||||
type: "message",
|
||||
tokensCovered: 230,
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**Execution Process for Example 3 with Cache Point Preservation:**
|
||||
|
||||
1. The system prompt evaluation remains the same (no cache point used).
|
||||
2. The algorithm detects that `previousCachePointPlacements` is provided in the config.
|
||||
3. It analyzes the previous cache point placements and the current message structure.
|
||||
4. Following the N-1 preservation rule, it decides to keep both previous cache points (at indices 2 and 4) since there are 3 total cache points available.
|
||||
5. It then calculates the optimal placement for the remaining cache point based on the new messages.
|
||||
6. Since there are 2 new messages with significant token count (210 tokens), it places a new cache point after the new user message.
|
||||
|
||||
**Output Cache Point Placements with Preservation:**
|
||||
|
||||
```javascript
|
||||
;[
|
||||
{
|
||||
index: 2, // After the second user message (What about deep learning?) - PRESERVED
|
||||
type: "message",
|
||||
tokensCovered: 240, // ~240 tokens covered (first 3 messages)
|
||||
},
|
||||
{
|
||||
index: 4, // After the third user message (How do neural networks work?) - PRESERVED
|
||||
type: "message",
|
||||
tokensCovered: 230, // ~230 tokens covered (messages between cache points)
|
||||
},
|
||||
{
|
||||
index: 6, // After the fourth user message (Can you explain backpropagation?) - NEW PLACEMENT
|
||||
type: "message",
|
||||
tokensCovered: 210, // ~210 tokens covered (messages between cache points)
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
**Resulting Message Structure with Preservation:**
|
||||
|
||||
```
|
||||
[User]: Tell me about machine learning.
|
||||
[Assistant]: Machine learning is a field of study...
|
||||
[User]: What about deep learning?
|
||||
[CACHE POINT 1] - PRESERVED FROM PREVIOUS
|
||||
[Assistant]: Deep learning is a subset of machine learning...
|
||||
[User]: How do neural networks work?
|
||||
[CACHE POINT 2] - PRESERVED FROM PREVIOUS
|
||||
[Assistant]: Neural networks are composed of layers of nodes...
|
||||
[User]: Can you explain backpropagation?
|
||||
[CACHE POINT 3] - NEW PLACEMENT
|
||||
[Assistant]: Backpropagation is an algorithm used to train neural networks...
|
||||
```
|
||||
|
||||
**Note**: The algorithm preserved both cache points from the previous conversation and placed a new cache point for the new messages. This ensures maximum cache hit rates while still adapting to the growing conversation.
|
||||
|
||||
### Example 4: Adding Messages (With Token Comparison)
|
||||
|
||||
In this example, we'll demonstrate how the algorithm handles the case when new messages have a token count small enough that cache points should not be changed:
|
||||
|
||||
**Updated Input Configuration with Previous Cache Points:**
|
||||
|
||||
```javascript
|
||||
const config = {
|
||||
// Same modelInfo and systemPrompt as before
|
||||
messages: [
|
||||
// Previous 10 messages...
|
||||
{
|
||||
role: "user",
|
||||
content: "Can you explain the difference between supervised and unsupervised learning in detail?",
|
||||
}, // ~80 tokens
|
||||
{
|
||||
role: "assistant",
|
||||
content:
|
||||
"Certainly! Supervised learning and unsupervised learning are two fundamental paradigms in machine learning with..",
|
||||
}, // ~130 tokens
|
||||
],
|
||||
usePromptCache: true,
|
||||
// Pass the previous cache point placements from Example 3
|
||||
previousCachePointPlacements: [
|
||||
{
|
||||
index: 2, // After the second user message
|
||||
type: "message",
|
||||
tokensCovered: 240,
|
||||
},
|
||||
{
|
||||
index: 6, // After the fourth user message
|
||||
type: "message",
|
||||
tokensCovered: 440,
|
||||
},
|
||||
{
|
||||
index: 8, // After the fifth user message
|
||||
type: "message",
|
||||
tokensCovered: 260,
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**Execution Process for Example 4 with Token Comparison:**
|
||||
|
||||
1. The algorithm detects that all cache points are used and new messages have been added.
|
||||
2. It calculates the token count of the new messages (210 tokens).
|
||||
3. It analyzes the token distribution between existing cache points and finds the smallest gap (260 tokens).
|
||||
4. It compares the token count of new messages (210) with the smallest gap (260).
|
||||
5. Since the new messages have less tokens than the smallest gap (210 < 260), it decides not to re-allocate cache points
|
||||
6. All existing cache points are preserved, and no cache point is allocated for the new messages.
|
||||
|
||||
**Output Cache Point Placements (Unchanged):**
|
||||
|
||||
```javascript
|
||||
;[
|
||||
{
|
||||
index: 2, // After the second user message - PRESERVED
|
||||
type: "message",
|
||||
tokensCovered: 240,
|
||||
},
|
||||
{
|
||||
index: 6, // After the fourth user message - PRESERVED
|
||||
type: "message",
|
||||
tokensCovered: 440,
|
||||
},
|
||||
{
|
||||
index: 8, // After the fifth user message - PRESERVED
|
||||
type: "message",
|
||||
tokensCovered: 260,
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
**Resulting Message Structure:**
|
||||
|
||||
```
|
||||
[User]: Tell me about machine learning.
|
||||
[Assistant]: Machine learning is a field of study...
|
||||
[User]: What about deep learning?
|
||||
[CACHE POINT 1] - PRESERVED
|
||||
[Assistant]: Deep learning is a subset of machine learning...
|
||||
[User]: How do neural networks work?
|
||||
[Assistant]: Neural networks are composed of layers of nodes...
|
||||
[User]: Can you explain backpropagation?
|
||||
[CACHE POINT 2] - PRESERVED
|
||||
[Assistant]: Backpropagation is an algorithm used to train neural networks...
|
||||
[User]: What are some applications of deep learning?
|
||||
[CACHE POINT 3] - PRESERVED
|
||||
[Assistant]: Deep learning has many applications including...
|
||||
[User]: Can you explain the difference between supervised and unsupervised learning in detail?
|
||||
[Assistant]: Certainly! Supervised learning and unsupervised learning are two fundamental paradigms in machine learning with...
|
||||
```
|
||||
|
||||
**Note**: In this case, the algorithm determined that the new messages are the smallest portion of the message history in comparison to existing cache points. Restructuring the cache points to make room to cache the new messages would be a net negative since it would not make use of 2 previously cached blocks, would have to re-write those 2 as a single cache point, and would write a new small cache point that would be chosen to be merged in the next round of messages.
|
||||
|
||||
### Example 5: Adding Messages that reallocate cache points
|
||||
|
||||
Now let's see what happens when we add messages with a larger token count:
|
||||
|
||||
**Updated Input Configuration with Previous Cache Points:**
|
||||
|
||||
```javascript
|
||||
const config = {
|
||||
// Same modelInfo and systemPrompt as before
|
||||
messages: [
|
||||
// Previous 10 messages...
|
||||
{
|
||||
role: "user",
|
||||
content: "Can you provide a detailed example of implementing a neural network for image classification?",
|
||||
}, // ~100 tokens
|
||||
{
|
||||
role: "assistant",
|
||||
content:
|
||||
"Certainly! Here's a detailed example of implementing a convolutional neural network (CNN) for image classification using TensorFlow and Keras...",
|
||||
}, // ~300 tokens
|
||||
],
|
||||
usePromptCache: true,
|
||||
// Pass the previous cache point placements from Example 3
|
||||
previousCachePointPlacements: [
|
||||
{
|
||||
index: 2, // After the second user message
|
||||
type: "message",
|
||||
tokensCovered: 240,
|
||||
},
|
||||
{
|
||||
index: 6, // After the fourth user message
|
||||
type: "message",
|
||||
tokensCovered: 440,
|
||||
},
|
||||
{
|
||||
index: 8, // After the fifth user message
|
||||
type: "message",
|
||||
tokensCovered: 260,
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**Execution Process for Example 5 with Token Comparison:**
|
||||
|
||||
1. The algorithm detects that all cache points are used and new messages have been added.
|
||||
2. It calculates the token count of the new messages (400 tokens).
|
||||
3. It analyzes the token distribution between existing cache points and finds the smallest gap (260 tokens).
|
||||
4. It calculates the required token threshold by applying a 20% increase to the smallest gap (260 \* 1.2 = 312).
|
||||
5. It compares the token count of new messages (400) with this threshold (312).
|
||||
6. Since the new messages have significantly more tokens than the threshold (400 > 312), it decides to combine cache points.
|
||||
7. It identifies that the cache point at index 8 has the smallest token coverage (260 tokens).
|
||||
8. It removes this cache point and places a new one after the new user message.
|
||||
|
||||
**Output Cache Point Placements with Reallocation:**
|
||||
|
||||
```javascript
|
||||
;[
|
||||
{
|
||||
index: 2, // After the second user message - PRESERVED
|
||||
type: "message",
|
||||
tokensCovered: 240,
|
||||
},
|
||||
{
|
||||
index: 6, // After the fourth user message - PRESERVED
|
||||
type: "message",
|
||||
tokensCovered: 440,
|
||||
},
|
||||
{
|
||||
index: 10, // After the sixth user message - NEW PLACEMENT
|
||||
type: "message",
|
||||
tokensCovered: 660, // Tokens from messages 7 through 10 (260 + 400)
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
**Resulting Message Structure:**
|
||||
|
||||
```
|
||||
[User]: Tell me about machine learning.
|
||||
[Assistant]: Machine learning is a field of study...
|
||||
[User]: What about deep learning?
|
||||
[CACHE POINT 1] - PRESERVED
|
||||
[Assistant]: Deep learning is a subset of machine learning...
|
||||
[User]: How do neural networks work?
|
||||
[Assistant]: Neural networks are composed of layers of nodes...
|
||||
[User]: Can you explain backpropagation?
|
||||
[CACHE POINT 2] - PRESERVED
|
||||
[Assistant]: Backpropagation is an algorithm used to train neural networks...
|
||||
[User]: What are some applications of deep learning?
|
||||
[Assistant]: Deep learning has many applications including...
|
||||
[User]: Can you provide a detailed example of implementing a neural network for image classification?
|
||||
[CACHE POINT 3] - NEW PLACEMENT
|
||||
[Assistant]: Certainly! Here's a detailed example of implementing a convolutional neural network (CNN) for image classification using TensorFlow and Keras...
|
||||
```
|
||||
|
||||
**Note**: In this case, the algorithm determined that it would be beneficial to reallocate a cache point for the new messages since they contain more tokens than the smallest gap between existing cache points. This optimization ensures that the most token-heavy parts of the conversation are cached.
|
||||
|
||||
**Important**: The `tokensCovered` value for each cache point represents the total number of tokens from the previous cache point (or the beginning of the conversation for the first cache point) up to the current cache point. For example, the cache point at index 10 covers 660 tokens, which includes all tokens from messages 7 through 10 (after the cache point at index 6 up to and including the cache point at index 10).
|
||||
|
||||
### Key Observations
|
||||
|
||||
1. **Simple Initial Placement Logic**: The last user message in the range that meets the minimum token threshold is set as a cache point.
|
||||
|
||||
2. **User Message Boundary Requirement**: Cache points are placed exclusively after user messages, not after assistant messages. This ensures cache points are placed at natural conversation boundaries where the user has provided input.
|
||||
|
||||
3. **Token Threshold Enforcement**: Each segment between cache points must meet the minimum token threshold (100 tokens in our examples) to be considered for caching. This is enforced by a guard clause that checks if the total tokens covered by a placement meets the minimum threshold.
|
||||
|
||||
4. **Adaptive Placement for Growing Conversations**: As the conversation grows, the strategy adapts by preserving previous cache points when possible and only reallocating them when beneficial.
|
||||
|
||||
5. **Token Comparison Optimization with Required Increase**: When all cache points are used and new messages are added, the algorithm compares the token count of new messages with the smallest combined token count of contiguous existing cache points, applying a required percentage increase (20%) to ensure reallocation is worth it. Cache points are only combined if the new messages have significantly more tokens than this threshold, ensuring that reallocation is only done when it results in a substantial net positive effect on caching efficiency.
|
||||
|
||||
This adaptive approach ensures that as conversations grow, the caching strategy continues to optimize token usage and response times by strategically placing cache points at the most effective positions, while avoiding inefficient reallocations that could result in a net negative effect on caching performance.
|
||||
|
|
@ -1,445 +0,0 @@
|
|||
# Bedrock Model Identification
|
||||
|
||||
This document explains how model information is identified and managed in the Amazon Bedrock provider implementation (`bedrock.ts`). It focuses on the sequence of operations that determine the `costModelConfig` property, which is crucial for token counting, pricing, and other features.
|
||||
|
||||
## Model Identification Flow
|
||||
|
||||
The `costModelConfig` property is set through different paths depending on the input configuration and response data from Bedrock. Below is a sequence diagram of how model identification works:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Constructor
|
||||
participant parseArn
|
||||
participant parseBaseModelId
|
||||
participant getModelById
|
||||
participant getModel
|
||||
|
||||
Constructor->>parseArn: Initialize (if awsCustomArn provided)
|
||||
parseArn->>parseBaseModelId: Extract region prefix from modelId
|
||||
parseBaseModelId-->>parseArn: Return modelId without prefix
|
||||
parseArn->>parseArn: Determine if cross-region inference
|
||||
parseArn-->>Constructor: Return arnInfo with modelId and crossRegionInference flag
|
||||
Constructor->>getModel: Call getModel()
|
||||
getModel->>getModelById: Lookup model
|
||||
getModelById-->>getModel: Return model info
|
||||
getModel-->>Constructor: Return model config
|
||||
Constructor->>Constructor: Set this.costModelConfig
|
||||
```
|
||||
|
||||
### During Stream Processing (with Prompt Router)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant createMessage
|
||||
participant parseArn
|
||||
participant getModelById
|
||||
|
||||
createMessage->>parseArn: Process stream event with invokedModelId
|
||||
parseArn->>parseArn: Extract modelId
|
||||
parseArn-->>createMessage: Return invokedModelArn
|
||||
createMessage->>getModelById: Call getModelById with invokedModelArn.modelId
|
||||
getModelById-->>createMessage: Return invokedModel
|
||||
createMessage->>createMessage: Set invokedModel.id = modelConfig.id
|
||||
createMessage->>createMessage: Set this.costModelConfig = invokedModel
|
||||
```
|
||||
|
||||
## Input Examples and Resulting Values
|
||||
|
||||
### Example 1: Standard Model Selection
|
||||
|
||||
**Input:**
|
||||
|
||||
```javascript
|
||||
const handler = new AwsBedrockHandler({
|
||||
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
awsAccessKey: "ACCESS_KEY",
|
||||
awsSecretKey: "SECRET_KEY",
|
||||
awsRegion: "us-east-1",
|
||||
})
|
||||
```
|
||||
|
||||
**Sequence:**
|
||||
|
||||
1. Constructor initializes with options
|
||||
2. Constructor calls `getModel()`
|
||||
3. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
4. `getModelById()` looks up the model in `bedrockModels`
|
||||
5. `this.costModelConfig` is set to:
|
||||
```javascript
|
||||
{
|
||||
id: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
info: {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
// other model properties...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Custom ARN for Foundation Model
|
||||
|
||||
**Input:**
|
||||
|
||||
```javascript
|
||||
const handler = new AwsBedrockHandler({
|
||||
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
awsAccessKey: "ACCESS_KEY",
|
||||
awsSecretKey: "SECRET_KEY",
|
||||
awsRegion: "us-east-1",
|
||||
awsCustomArn: "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
})
|
||||
```
|
||||
|
||||
**Sequence:**
|
||||
|
||||
1. Constructor initializes with options
|
||||
2. Constructor calls `parseArn("arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
3. `parseArn()` extracts:
|
||||
```javascript
|
||||
{
|
||||
isValid: true,
|
||||
region: "us-east-1",
|
||||
modelType: "foundation-model",
|
||||
modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
crossRegionInference: false
|
||||
}
|
||||
```
|
||||
4. Constructor sets `this.arnInfo` to the result
|
||||
5. Constructor calls `getModel()`
|
||||
6. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
7. `getModelById()` looks up the model in `bedrockModels`
|
||||
8. `this.costModelConfig` is set to:
|
||||
```javascript
|
||||
{
|
||||
id: "anthropic.claude-3-5-sonnet-20241022-v2:0", // Note: ID is not the ARN since it's a foundation-model
|
||||
info: {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
// other model properties...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Custom ARN for Prompt Router
|
||||
|
||||
**Input:**
|
||||
|
||||
```javascript
|
||||
const handler = new AwsBedrockHandler({
|
||||
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
awsAccessKey: "ACCESS_KEY",
|
||||
awsSecretKey: "SECRET_KEY",
|
||||
awsRegion: "us-west-2",
|
||||
awsCustomArn: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router",
|
||||
})
|
||||
```
|
||||
|
||||
**Sequence:**
|
||||
|
||||
1. Constructor initializes with options
|
||||
2. Constructor calls `parseArn("arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router")`
|
||||
3. `parseArn()` extracts:
|
||||
```javascript
|
||||
{
|
||||
isValid: true,
|
||||
region: "us-west-2",
|
||||
modelType: "prompt-router",
|
||||
modelId: "my-router",
|
||||
crossRegionInference: false
|
||||
}
|
||||
```
|
||||
4. Constructor sets `this.arnInfo` to the result
|
||||
5. Constructor calls `getModel()`
|
||||
6. `getModel()` calls `getModelById("my-router")`
|
||||
7. `getModelById()` doesn't find "my-router" in `bedrockModels`, returns default model info
|
||||
8. Since `this.arnInfo.modelType` is "prompt-router" (not "foundation-model"), `getModel()` sets the ID to the full ARN
|
||||
9. `this.costModelConfig` is set to:
|
||||
```javascript
|
||||
{
|
||||
id: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router", // Full ARN as ID
|
||||
info: {
|
||||
// Default model info for prompt routers
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
// other model properties...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Cross-Region Inference
|
||||
|
||||
**Input:**
|
||||
|
||||
```javascript
|
||||
const handler = new AwsBedrockHandler({
|
||||
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
awsAccessKey: "ACCESS_KEY",
|
||||
awsSecretKey: "SECRET_KEY",
|
||||
awsRegion: "eu-west-1",
|
||||
awsUseCrossRegionInference: true,
|
||||
})
|
||||
```
|
||||
|
||||
**Sequence:**
|
||||
|
||||
1. Constructor initializes with options
|
||||
2. Constructor calls `getModel()`
|
||||
3. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
4. `getModelById()` looks up the model in `bedrockModels`
|
||||
5. Since `awsUseCrossRegionInference` is true, `getModel()` gets the prefix for "eu-west-1" (which is "eu.")
|
||||
6. `getModel()` prepends "eu." to the model ID
|
||||
7. `this.costModelConfig` is set to:
|
||||
```javascript
|
||||
{
|
||||
id: "eu.anthropic.claude-3-5-sonnet-20241022-v2:0", // Note the "eu." prefix
|
||||
info: {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
// other model properties...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 5: Prompt Router with invokedModelId in Stream
|
||||
|
||||
**Initial Input:**
|
||||
|
||||
```javascript
|
||||
const handler = new AwsBedrockHandler({
|
||||
awsAccessKey: "ACCESS_KEY",
|
||||
awsSecretKey: "SECRET_KEY",
|
||||
awsRegion: "us-west-2",
|
||||
awsCustomArn: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router",
|
||||
})
|
||||
```
|
||||
|
||||
**Initial Sequence (same as Example 3):**
|
||||
|
||||
1. `this.costModelConfig` is initially set to:
|
||||
```javascript
|
||||
{
|
||||
id: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router",
|
||||
info: {
|
||||
// Default model info for prompt routers
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
// other properties...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Stream Event with invokedModelId:**
|
||||
|
||||
```javascript
|
||||
{
|
||||
trace: {
|
||||
promptRouter: {
|
||||
invokedModelId: "arn:aws:bedrock:us-west-2:123456789012:inference-profile/anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
usage: {
|
||||
inputTokens: 150,
|
||||
outputTokens: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Stream Processing Sequence:**
|
||||
|
||||
1. `createMessage()` encounters the stream event with `invokedModelId`
|
||||
2. It calls `parseArn("arn:aws:bedrock:us-west-2:123456789012:inference-profile/anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
3. `parseArn()` extracts:
|
||||
```javascript
|
||||
{
|
||||
isValid: true,
|
||||
region: "us-west-2",
|
||||
modelType: "inference-profile",
|
||||
modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
crossRegionInference: false
|
||||
}
|
||||
```
|
||||
4. `createMessage()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
5. `getModelById()` looks up the model in `bedrockModels` and returns the model info
|
||||
6. `createMessage()` sets `invokedModel.id` to the original router ID
|
||||
7. `this.costModelConfig` is updated to:
|
||||
```javascript
|
||||
{
|
||||
id: "arn:aws:bedrock:us-west-2:123456789012:prompt-router/my-router", // Keeps router ID
|
||||
info: {
|
||||
// Claude 3.5 Sonnet model info
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
// other Claude-specific properties...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This ensures that:
|
||||
|
||||
1. Subsequent requests continue to use the prompt router
|
||||
2. Token counting and pricing use the actual model's rates
|
||||
3. Context window and other model-specific properties are correctly set
|
||||
|
||||
### Example 6: Cross-Region ARN with Region Prefix
|
||||
|
||||
**Input:**
|
||||
|
||||
```javascript
|
||||
const handler = new AwsBedrockHandler({
|
||||
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
awsAccessKey: "ACCESS_KEY",
|
||||
awsSecretKey: "SECRET_KEY",
|
||||
awsRegion: "us-east-1",
|
||||
awsCustomArn:
|
||||
"arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
})
|
||||
```
|
||||
|
||||
**Sequence:**
|
||||
|
||||
1. Constructor initializes with options
|
||||
2. Constructor calls `parseArn("arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
3. `parseArn()` extracts region and calls `parseBaseModelId("us.anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
4. `parseBaseModelId()` recognizes "us." as a region prefix and removes it
|
||||
5. `parseArn()` returns:
|
||||
```javascript
|
||||
{
|
||||
isValid: true,
|
||||
region: "us-west-2",
|
||||
modelType: "inference-profile",
|
||||
modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", // Note: prefix removed
|
||||
crossRegionInference: true // Detected cross-region
|
||||
}
|
||||
```
|
||||
6. Constructor sets `this.arnInfo` to the result and updates `this.options.awsRegion` to "us-west-2"
|
||||
7. Constructor calls `getModel()`
|
||||
8. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
9. `getModelById()` looks up the model in `bedrockModels`
|
||||
10. Since `this.arnInfo.modelType` is "inference-profile" (not "foundation-model"), `getModel()` sets the ID to the full ARN
|
||||
11. `this.costModelConfig` is set to:
|
||||
```javascript
|
||||
{
|
||||
id: "arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-20241022-v2:0", // Full ARN
|
||||
info: {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
// other model properties...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 7: Single-Region ARN with Region Prefix (apne3)
|
||||
|
||||
**Input:**
|
||||
|
||||
```javascript
|
||||
const handler = new AwsBedrockHandler({
|
||||
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
awsAccessKey: "ACCESS_KEY",
|
||||
awsSecretKey: "SECRET_KEY",
|
||||
awsRegion: "ap-northeast-3", // Osaka region
|
||||
awsCustomArn:
|
||||
"arn:aws:bedrock:ap-northeast-3:123456789012:inference-profile/apne3.anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
})
|
||||
```
|
||||
|
||||
**Sequence:**
|
||||
|
||||
1. Constructor initializes with options
|
||||
2. Constructor calls `parseArn("arn:aws:bedrock:ap-northeast-3:123456789012:inference-profile/apne3.anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
3. `parseArn()` extracts region and calls `parseBaseModelId("apne3.anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
4. `parseBaseModelId()` recognizes "apne3." as a region prefix and removes it
|
||||
5. `parseArn()` returns:
|
||||
```javascript
|
||||
{
|
||||
isValid: true,
|
||||
region: "ap-northeast-3",
|
||||
modelType: "inference-profile",
|
||||
modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", // Note: prefix removed
|
||||
crossRegionInference: false // Not a cross-region prefix since apne3 maps to a single region
|
||||
}
|
||||
```
|
||||
6. Constructor sets `this.arnInfo` to the result
|
||||
7. Constructor calls `getModel()`
|
||||
8. `getModel()` calls `getModelById("anthropic.claude-3-5-sonnet-20241022-v2:0")`
|
||||
9. `getModelById()` looks up the model in `bedrockModels`
|
||||
10. Since `this.arnInfo.modelType` is "inference-profile" (not "foundation-model"), `getModel()` sets the ID to the full ARN
|
||||
11. `this.costModelConfig` is set to:
|
||||
```javascript
|
||||
{
|
||||
id: "arn:aws:bedrock:ap-northeast-3:123456789012:inference-profile/apne3.anthropic.claude-3-5-sonnet-20241022-v2:0", // Full ARN
|
||||
info: {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
// other model properties...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Region Prefixes
|
||||
|
||||
The system recognizes these region prefixes for cross-region inference:
|
||||
|
||||
| Prefix | Region ID | Description | Multi-Region |
|
||||
| -------- | ---------------- | ------------------------- | ------------ |
|
||||
| "us." | "us-east-1" | US East (N. Virginia) | Yes |
|
||||
| "use1." | "us-east-1" | US East (N. Virginia) | No |
|
||||
| "use2." | "us-east-2" | US East (Ohio) | No |
|
||||
| "usw2." | "us-west-2" | US West (Oregon) | No |
|
||||
| "eu." | "eu-west-1" | Europe (Ireland) | Yes |
|
||||
| "euw1." | "eu-west-1" | Europe (Ireland) | No |
|
||||
| "ap." | "ap-southeast-1" | Asia Pacific (Singapore) | Yes |
|
||||
| "apne1." | "ap-northeast-1" | Asia Pacific (Tokyo) | No |
|
||||
| "apne3." | "ap-northeast-3" | Asia Pacific (Osaka) | No |
|
||||
| "ca." | "ca-central-1" | Canada (Central) | Yes |
|
||||
| "sa." | "sa-east-1" | South America (São Paulo) | Yes |
|
||||
| "apac." | "ap-southeast-1" | Default APAC region | Yes |
|
||||
| "emea." | "eu-west-1" | Default EMEA region | Yes |
|
||||
| "amer." | "us-east-1" | Default Americas region | Yes |
|
||||
|
||||
These prefixes are used to:
|
||||
|
||||
1. Identify and strip region prefixes from model IDs in `parseBaseModelId()`
|
||||
2. Add appropriate region prefixes when cross-region inference is enabled in `getModel()`
|
||||
|
||||
**Note on Multi-Region Prefixes:**
|
||||
|
||||
- Prefixes marked as "Multi-Region" (like "us.", "eu.", "ap.", etc.) set the `crossRegionInference` flag to `true` when detected in an ARN
|
||||
- These prefixes typically represent a geographic area with multiple AWS regions
|
||||
- Single-region prefixes (like "apne3.", "use1.", etc.) set the `crossRegionInference` flag to `false`
|
||||
- The `crossRegionInference` flag affects how the system handles region-specific model configurations
|
||||
|
||||
## Summary
|
||||
|
||||
The Bedrock provider's model identification system follows these key principles:
|
||||
|
||||
1. **ARN Parsing**: Extracts model ID, region, and resource type from ARNs
|
||||
2. **Model Lookup**: Uses the extracted model ID to find model information
|
||||
3. **ID Preservation**:
|
||||
- For foundation models: Uses the model ID directly
|
||||
- For other resources (prompt routers, inference profiles): Uses the full ARN as the ID
|
||||
4. **Cross-Region Handling**: Adds or removes region prefixes as needed
|
||||
5. **Dynamic Updates**: Updates model information when a prompt router provides an invokedModelId
|
||||
|
||||
This system ensures that:
|
||||
|
||||
- The correct model ID is used for API requests
|
||||
- Accurate model information is used for token counting and pricing
|
||||
- Cross-region inference works correctly
|
||||
- Prompt routers can dynamically select models while maintaining proper tracking
|
||||
443
docs/settings.md
|
|
@ -1,443 +0,0 @@
|
|||
## For All Settings
|
||||
|
||||
1. Add the setting to schema definitions:
|
||||
|
||||
- Add the item to `globalSettingsSchema` in `src/schemas/index.ts`
|
||||
- Add the item to `globalSettingsRecord` in `src/schemas/index.ts`
|
||||
- Example: `terminalCommandDelay: z.number().optional(),`
|
||||
|
||||
2. Add the setting to type definitions:
|
||||
|
||||
- Add the item to `src/exports/types.ts`
|
||||
- Add the item to `src/exports/roo-code.d.ts`
|
||||
- Add the setting to `src/shared/ExtensionMessage.ts`
|
||||
- Add the setting to the WebviewMessage type in `src/shared/WebviewMessage.ts`
|
||||
- Example: `terminalCommandDelay?: number | undefined`
|
||||
|
||||
3. Add test coverage:
|
||||
- Add the setting to mockState in src/core/webview/**tests**/ClineProvider.test.ts
|
||||
- Add test cases for setting persistence and state updates
|
||||
- Ensure all tests pass before submitting changes
|
||||
|
||||
## For Checkbox Settings
|
||||
|
||||
1. Add the message type to src/shared/WebviewMessage.ts:
|
||||
|
||||
- Add the setting name to the WebviewMessage type's type union
|
||||
- Example: `| "multisearchDiffEnabled"`
|
||||
|
||||
2. Add the setting to webview-ui/src/context/ExtensionStateContext.tsx:
|
||||
|
||||
- Add the setting to the ExtensionStateContextType interface
|
||||
- Add the setter function to the interface
|
||||
- Add the setting to the initial state in useState
|
||||
- Add the setting to the contextValue object
|
||||
- Example:
|
||||
```typescript
|
||||
interface ExtensionStateContextType {
|
||||
multisearchDiffEnabled: boolean
|
||||
setMultisearchDiffEnabled: (value: boolean) => void
|
||||
}
|
||||
```
|
||||
|
||||
3. Add the setting to src/core/webview/ClineProvider.ts:
|
||||
|
||||
- Add the setting name to the GlobalStateKey type union
|
||||
- Add the setting to the Promise.all array in getState
|
||||
- Add the setting to the return value in getState with a default value
|
||||
- Add the setting to the destructured variables in getStateToPostToWebview
|
||||
- Add the setting to the return value in getStateToPostToWebview
|
||||
- Add a case in setWebviewMessageListener to handle the setting's message type
|
||||
- Example:
|
||||
```typescript
|
||||
case "multisearchDiffEnabled":
|
||||
await this.updateGlobalState("multisearchDiffEnabled", message.bool)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
```
|
||||
|
||||
4. Add the checkbox UI to webview-ui/src/components/settings/SettingsView.tsx:
|
||||
|
||||
- Import the setting and its setter from ExtensionStateContext
|
||||
- Add the VSCodeCheckbox component with the setting's state and onChange handler
|
||||
- Add appropriate labels and description text
|
||||
- Example:
|
||||
```typescript
|
||||
<VSCodeCheckbox
|
||||
checked={multisearchDiffEnabled}
|
||||
onChange={(e: any) => setMultisearchDiffEnabled(e.target.checked)}
|
||||
>
|
||||
<span style={{ fontWeight: "500" }}>Enable multi-search diff matching</span>
|
||||
</VSCodeCheckbox>
|
||||
```
|
||||
|
||||
5. Add the setting to handleSubmit in webview-ui/src/components/settings/SettingsView.tsx:
|
||||
|
||||
- Add a vscode.postMessage call to send the setting's value when clicking Save
|
||||
- This step is critical for persistence - without it, the setting will not be saved when the user clicks Save
|
||||
- Example:
|
||||
```typescript
|
||||
vscode.postMessage({ type: "multisearchDiffEnabled", bool: multisearchDiffEnabled })
|
||||
```
|
||||
|
||||
6. Style Considerations:
|
||||
- Use the VSCodeCheckbox component from @vscode/webview-ui-toolkit/react instead of HTML input elements
|
||||
- Wrap each checkbox in a div element for proper spacing
|
||||
- Use a span with className="font-medium" for the checkbox label inside the VSCodeCheckbox component
|
||||
- Place the description in a separate div with className="text-vscode-descriptionForeground text-sm mt-1"
|
||||
- Maintain consistent spacing between configuration options
|
||||
- Example:
|
||||
```typescript
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={terminalPowershellCounter ?? true}
|
||||
onChange={(e: any) => setCachedStateField("terminalPowershellCounter", e.target.checked)}
|
||||
data-testid="terminal-powershell-counter-checkbox">
|
||||
<span className="font-medium">{t("settings:terminal.powershellCounter.label")}</span>
|
||||
</VSCodeCheckbox>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:terminal.powershellCounter.description")}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## For Select/Dropdown Settings
|
||||
|
||||
1. Add the message type to src/shared/WebviewMessage.ts:
|
||||
|
||||
- Add the setting name to the WebviewMessage type's type union
|
||||
- Example: `| "preferredLanguage"`
|
||||
|
||||
2. Add the setting to webview-ui/src/context/ExtensionStateContext.tsx:
|
||||
|
||||
- Add the setting to the ExtensionStateContextType interface
|
||||
- Add the setter function to the interface
|
||||
- Add the setting to the initial state in useState with a default value
|
||||
- Add the setting to the contextValue object
|
||||
- Example:
|
||||
```typescript
|
||||
interface ExtensionStateContextType {
|
||||
preferredLanguage: string
|
||||
setPreferredLanguage: (value: string) => void
|
||||
}
|
||||
```
|
||||
|
||||
3. Add the setting to src/core/webview/ClineProvider.ts:
|
||||
|
||||
- Add the setting name to the GlobalStateKey type union
|
||||
- Add the setting to the Promise.all array in getState
|
||||
- Add the setting to the return value in getState with a default value
|
||||
- Add the setting to the destructured variables in getStateToPostToWebview
|
||||
- Add the setting to the return value in getStateToPostToWebview
|
||||
- This step is critical for UI display - without it, the setting will not be displayed in the UI
|
||||
- Add a case in setWebviewMessageListener to handle the setting's message type
|
||||
- Example:
|
||||
```typescript
|
||||
case "preferredLanguage":
|
||||
await this.updateGlobalState("preferredLanguage", message.text)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
```
|
||||
|
||||
4. Add the select UI to webview-ui/src/components/settings/SettingsView.tsx:
|
||||
|
||||
- Import the setting and its setter from ExtensionStateContext
|
||||
- Add the select element with appropriate styling to match VSCode's theme
|
||||
- Add options for the dropdown
|
||||
- Add appropriate labels and description text
|
||||
- Example:
|
||||
```typescript
|
||||
<select
|
||||
value={preferredLanguage}
|
||||
onChange={(e) => setPreferredLanguage(e.target.value)}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "4px 8px",
|
||||
backgroundColor: "var(--vscode-input-background)",
|
||||
color: "var(--vscode-input-foreground)",
|
||||
border: "1px solid var(--vscode-input-border)",
|
||||
borderRadius: "2px"
|
||||
}}>
|
||||
<option value="English">English</option>
|
||||
<option value="Spanish">Spanish</option>
|
||||
...
|
||||
</select>
|
||||
```
|
||||
|
||||
5. Add the setting to handleSubmit in webview-ui/src/components/settings/SettingsView.tsx:
|
||||
- Add a vscode.postMessage call to send the setting's value when clicking Done
|
||||
- Example:
|
||||
```typescript
|
||||
vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage })
|
||||
```
|
||||
|
||||
These steps ensure that:
|
||||
|
||||
- The setting's state is properly typed throughout the application
|
||||
- The setting persists between sessions
|
||||
- The setting's value is properly synchronized between the webview and extension
|
||||
- The setting has a proper UI representation in the settings view
|
||||
- Test coverage is maintained for the new setting
|
||||
|
||||
## Adding a New Configuration Item: Summary of Required Changes
|
||||
|
||||
To add a new configuration item to the system, the following changes are necessary:
|
||||
|
||||
1. **Feature-Specific Class** (if applicable)
|
||||
|
||||
- For settings that affect specific features (e.g., Terminal, Browser, etc.)
|
||||
- Add a static property to store the value
|
||||
- Add getter/setter methods to access and modify the value
|
||||
|
||||
2. **Schema Definition**
|
||||
|
||||
- Add the item to globalSettingsSchema in src/schemas/index.ts
|
||||
- Add the item to globalSettingsRecord in src/schemas/index.ts
|
||||
|
||||
3. **Type Definitions**
|
||||
|
||||
- Add the item to src/exports/types.ts
|
||||
- Add the item to src/exports/roo-code.d.ts
|
||||
- Add the item to src/shared/ExtensionMessage.ts
|
||||
- Add the item to src/shared/WebviewMessage.ts
|
||||
|
||||
4. **UI Component**
|
||||
|
||||
- Create or update a component in webview-ui/src/components/settings/
|
||||
- Add appropriate slider/input controls with min/max/step values
|
||||
- Ensure the props are passed correctly to the component in webview-ui/src/components/settings/SettingsView.tsx
|
||||
- Update the component's props interface to include the new settings
|
||||
|
||||
5. **Translations**
|
||||
|
||||
- Add label and description in webview-ui/src/i18n/locales/en/settings.json
|
||||
- Update all other languages
|
||||
- If any language content is changed, synchronize all other languages with that change
|
||||
- Translations must be performed within "translation" mode so change modes for that purpose
|
||||
|
||||
6. **State Management**
|
||||
|
||||
- Add the item to the destructuring in SettingsView.tsx
|
||||
- Add the item to the handleSubmit function in webview-ui/src/components/settings/SettingsView.tsx
|
||||
- Add the item to getStateToPostToWebview in src/core/webview/ClineProvider.ts
|
||||
- Add the item to getState in src/core/webview/ClineProvider.ts with appropriate default values
|
||||
- Add the item to the initialization in resolveWebviewView in src/core/webview/ClineProvider.ts
|
||||
|
||||
7. **Message Handling**
|
||||
|
||||
- Add a case for the item in src/core/webview/webviewMessageHandler.ts
|
||||
|
||||
8. **Implementation-Specific Logic**
|
||||
|
||||
- Implement any feature-specific behavior triggered by the setting
|
||||
- Examples:
|
||||
- Environment variables for terminal settings
|
||||
- API configuration changes for provider settings
|
||||
- UI behavior modifications for display settings
|
||||
|
||||
9. **Testing**
|
||||
|
||||
- Add test cases for the new settings in appropriate test files
|
||||
- Verify settings persistence and state updates
|
||||
|
||||
10. **Ensuring Settings Persistence Across Reload**
|
||||
|
||||
To ensure settings persist across application reload, several key components must be properly configured:
|
||||
|
||||
1. **Initial State in ExtensionStateContextProvider**:
|
||||
|
||||
- Add the setting to the initial state in the useState call
|
||||
- Example:
|
||||
```typescript
|
||||
const [state, setState] = useState<ExtensionState>({
|
||||
// existing settings...
|
||||
newSetting: false, // Default value for the new setting
|
||||
})
|
||||
```
|
||||
|
||||
2. **State Loading in ClineProvider**:
|
||||
|
||||
- Add the setting to the getState method to load it from storage
|
||||
- Example:
|
||||
```typescript
|
||||
return {
|
||||
// existing settings...
|
||||
newSetting: stateValues.newSetting ?? false,
|
||||
}
|
||||
```
|
||||
|
||||
3. **State Initialization in resolveWebviewView**:
|
||||
|
||||
- Add the setting to the initialization in resolveWebviewView
|
||||
- Example:
|
||||
```typescript
|
||||
this.getState().then(
|
||||
({
|
||||
// existing settings...
|
||||
newSetting,
|
||||
}) => {
|
||||
// Initialize the setting with its stored value or default
|
||||
FeatureClass.setNewSetting(newSetting ?? false)
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
4. **State Transmission to Webview**:
|
||||
|
||||
- Add the setting to the getStateToPostToWebview method
|
||||
- Example:
|
||||
```typescript
|
||||
return {
|
||||
// existing settings...
|
||||
newSetting: newSetting ?? false,
|
||||
}
|
||||
```
|
||||
|
||||
5. **Setter Method in ExtensionStateContext**:
|
||||
- Add the setter method to the contextValue object
|
||||
- Example:
|
||||
```typescript
|
||||
const contextValue: ExtensionStateContextType = {
|
||||
// existing properties and methods...
|
||||
setNewSetting: (value) => setState((prevState) => ({ ...prevState, newSetting: value })),
|
||||
}
|
||||
```
|
||||
|
||||
11. **Debugging Settings Persistence Issues**
|
||||
|
||||
If a setting is not persisting across reload, check the following:
|
||||
|
||||
1. **Complete Chain of Persistence**:
|
||||
|
||||
- Verify that the setting is added to all required locations:
|
||||
- globalSettingsSchema and globalSettingsRecord in src/schemas/index.ts
|
||||
- Initial state in ExtensionStateContextProvider
|
||||
- getState method in src/core/webview/ClineProvider.ts
|
||||
- getStateToPostToWebview method in src/core/webview/ClineProvider.ts
|
||||
- resolveWebviewView method in src/core/webview/ClineProvider.ts (if feature-specific)
|
||||
- A break in any part of this chain can prevent persistence
|
||||
|
||||
2. **Default Values Consistency**:
|
||||
|
||||
- Ensure default values are consistent across all locations
|
||||
- Inconsistent defaults can cause unexpected behavior
|
||||
|
||||
3. **Message Handling**:
|
||||
|
||||
- Confirm the src/core/webview/webviewMessageHandler.ts has a case for the setting
|
||||
- Verify the message type matches what's sent from the UI
|
||||
|
||||
4. **UI Integration**:
|
||||
|
||||
- Check that the setting is included in the handleSubmit function in webview-ui/src/components/settings/SettingsView.tsx
|
||||
- Ensure the UI component correctly updates the state
|
||||
|
||||
5. **Type Definitions**:
|
||||
|
||||
- Verify the setting is properly typed in all relevant interfaces
|
||||
- Check for typos in property names across different files
|
||||
|
||||
6. **Storage Mechanism**:
|
||||
- For complex settings, ensure proper serialization/deserialization
|
||||
- Check that the setting is being correctly stored in VSCode's globalState
|
||||
|
||||
These checks help identify and resolve common issues with settings persistence.
|
||||
|
||||
12. **Advanced Troubleshooting: The Complete Settings Persistence Chain**
|
||||
|
||||
Settings persistence requires a complete chain of state management across multiple components. Understanding this chain is critical for both humans and AI to effectively troubleshoot persistence issues:
|
||||
|
||||
1. **Schema Definition (Entry Point)**:
|
||||
|
||||
- Settings must be properly defined in `globalSettingsSchema` and `globalSettingsRecord`
|
||||
- Enum values should use proper zod schemas: `z.enum(["value1", "value2"])`
|
||||
- Example:
|
||||
|
||||
```typescript
|
||||
// In src/schemas/index.ts
|
||||
export const globalSettingsSchema = z.object({
|
||||
// Existing settings...
|
||||
commandRiskLevel: z.enum(["readOnly", "reversibleChanges", "complexChanges"]).optional(),
|
||||
})
|
||||
|
||||
const globalSettingsRecord: GlobalSettingsRecord = {
|
||||
// Existing settings...
|
||||
commandRiskLevel: undefined,
|
||||
}
|
||||
```
|
||||
|
||||
2. **UI Component (User Interaction)**:
|
||||
|
||||
- Must use consistent components (Select vs. select) with other similar settings
|
||||
- Must use `setCachedStateField` for state updates, not direct state setting
|
||||
- Must generate the correct message type through `vscode.postMessage`
|
||||
- Example:
|
||||
```tsx
|
||||
// In a settings component
|
||||
<Select value={commandRiskLevel} onValueChange={(value) => setCachedStateField("commandRiskLevel", value)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="readOnly">{t("label.readOnly")}</SelectItem>
|
||||
{/* Other options... */}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
```
|
||||
|
||||
3. **Message Handler (State Saving)**:
|
||||
|
||||
- Must use correct message type in `src/core/webview/webviewMessageHandler.ts`
|
||||
- Must use `updateGlobalState` with properly typed values
|
||||
- Must call `postStateToWebview` after updates
|
||||
- Example:
|
||||
```typescript
|
||||
// In src/core/webview/webviewMessageHandler.ts
|
||||
case "commandRiskLevel":
|
||||
await updateGlobalState(
|
||||
"commandRiskLevel",
|
||||
(message.text ?? "readOnly") as "readOnly" | "reversibleChanges" | "complexChanges"
|
||||
)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
```
|
||||
|
||||
4. **State Retrieval (Reading State)**:
|
||||
|
||||
- In `getState`, state must be properly retrieved from stateValues
|
||||
- In `getStateToPostToWebview`, the setting must be in the destructured parameters
|
||||
- The setting must be included in the return value
|
||||
- Use `contextProxy.getGlobalState` for direct access when needed
|
||||
- Example:
|
||||
|
||||
```typescript
|
||||
// In src/core/webview/ClineProvider.ts getStateToPostToWebview
|
||||
const {
|
||||
// Other state properties...
|
||||
commandRiskLevel,
|
||||
} = await this.getState()
|
||||
|
||||
return {
|
||||
// Other state properties...
|
||||
commandRiskLevel: commandRiskLevel ?? "readOnly",
|
||||
}
|
||||
```
|
||||
|
||||
5. **Debugging Strategies**:
|
||||
|
||||
- **Follow the State Flow**: Watch the setting's value at each step in the chain
|
||||
- **Type Safety**: Ensure the same type is used throughout the chain
|
||||
- **Component Consistency**: Use the same pattern as other working settings
|
||||
- **Check Return Values**: Ensure the setting is included in all return objects
|
||||
- **State vs. Configuration**: Understand when to use state vs. VSCode configuration
|
||||
|
||||
6. **Common Pitfalls**:
|
||||
- **Type Mismatch**: Using string where an enum is expected
|
||||
- **Chain Breaks**: Missing the setting in return objects
|
||||
- **UI Inconsistency**: Using different component patterns
|
||||
- **DefaultValue Issues**: Inconsistent default values across components
|
||||
- **Missing Schema**: Not adding to schema or record definitions
|
||||
|
||||
Remember: A break at ANY point in this chain can cause persistence failures. When troubleshooting, systematically check each link in the chain to identify where the issue occurs.
|
||||
23
e2e/.eslintrc.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{
|
||||
"selector": "import",
|
||||
"format": ["camelCase", "PascalCase"]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": "off",
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off"
|
||||
},
|
||||
"ignorePatterns": ["out"]
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
# VSCode Integration Tests
|
||||
|
||||
This document describes the integration test setup for the Roo Code VSCode extension.
|
||||
|
||||
## Overview
|
||||
|
||||
The integration tests use the `@vscode/test-electron` package to run tests in a real VSCode environment. These tests verify that the extension works correctly within VSCode, including features like mode switching, webview interactions, and API communication.
|
||||
|
||||
## Test Setup
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
e2e/src/
|
||||
├── runTest.ts # Main test runner
|
||||
├── suite/
|
||||
│ ├── index.ts # Test suite configuration
|
||||
│ ├── modes.test.ts # Mode switching tests
|
||||
│ ├── tasks.test.ts # Task execution tests
|
||||
│ └── extension.test.ts # Extension activation tests
|
||||
```
|
||||
|
||||
### Test Runner Configuration
|
||||
|
||||
The test runner (`runTest.ts`) is responsible for:
|
||||
|
||||
- Setting up the extension development path
|
||||
- Configuring the test environment
|
||||
- Running the integration tests using `@vscode/test-electron`
|
||||
|
||||
### Environment Setup
|
||||
|
||||
1. Create a `.env.local` file in the root directory with required environment variables:
|
||||
|
||||
```
|
||||
OPENROUTER_API_KEY=sk-or-v1-...
|
||||
```
|
||||
|
||||
2. The test suite (`suite/index.ts`) configures:
|
||||
|
||||
- Mocha test framework with TDD interface
|
||||
- 10-minute timeout for LLM communication
|
||||
- Global extension API access
|
||||
- WebView panel setup
|
||||
- OpenRouter API configuration
|
||||
|
||||
## Test Suite Structure
|
||||
|
||||
Tests are organized using Mocha's TDD interface (`suite` and `test` functions). The main test files are:
|
||||
|
||||
- `modes.test.ts`: Tests mode switching functionality
|
||||
- `tasks.test.ts`: Tests task execution
|
||||
- `extension.test.ts`: Tests extension activation
|
||||
|
||||
### Global Objects
|
||||
|
||||
The following global objects are available in tests:
|
||||
|
||||
```typescript
|
||||
declare global {
|
||||
var api: RooCodeAPI
|
||||
var provider: ClineProvider
|
||||
var extension: vscode.Extension<RooCodeAPI>
|
||||
var panel: vscode.WebviewPanel
|
||||
}
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
1. Ensure you have the required environment variables set in `.env.local`
|
||||
|
||||
2. Run the integration tests:
|
||||
|
||||
```bash
|
||||
npm run test:integration
|
||||
```
|
||||
|
||||
3. If you want to run a specific test, you can use the `test.only` function in the test file. This will run only the test you specify and ignore the others. Be sure to remove the `test.only` function before committing your changes.
|
||||
|
||||
The tests will:
|
||||
|
||||
- Download and launch a clean VSCode instance
|
||||
- Install the extension
|
||||
- Execute the test suite
|
||||
- Report results
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
When writing new integration tests:
|
||||
|
||||
1. Create a new test file in `src/test/suite/` with the `.test.ts` extension
|
||||
|
||||
2. Structure your tests using the TDD interface:
|
||||
|
||||
```typescript
|
||||
import * as assert from "assert"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
suite("Your Test Suite Name", () => {
|
||||
test("Should do something specific", async function () {
|
||||
// Your test code here
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
3. Use the global objects (`api`, `provider`, `extension`, `panel`) to interact with the extension
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Timeouts**: Use appropriate timeouts for async operations:
|
||||
|
||||
```typescript
|
||||
const timeout = 30000
|
||||
const interval = 1000
|
||||
```
|
||||
|
||||
2. **State Management**: Reset extension state before/after tests:
|
||||
|
||||
```typescript
|
||||
await globalThis.api.setConfiguration({
|
||||
mode: "Ask",
|
||||
alwaysAllowModeSwitch: true,
|
||||
})
|
||||
```
|
||||
|
||||
3. **Assertions**: Use clear assertions with meaningful messages:
|
||||
|
||||
```typescript
|
||||
assert.ok(condition, "Descriptive message about what failed")
|
||||
```
|
||||
|
||||
4. **Error Handling**: Wrap test code in try/catch blocks and clean up resources:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
// Test code
|
||||
} finally {
|
||||
// Cleanup code
|
||||
}
|
||||
```
|
||||
|
||||
5. **Wait for Operations**: Use polling when waiting for async operations:
|
||||
|
||||
```typescript
|
||||
let startTime = Date.now()
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
if (condition) {
|
||||
break
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, interval))
|
||||
}
|
||||
```
|
||||
|
||||
6. **Grading**: When grading tests, use the `Grade:` format to ensure the test is graded correctly (See modes.test.ts for an example).
|
||||
|
||||
```typescript
|
||||
await globalThis.api.startNewTask({
|
||||
text: `Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output} \n Be sure to say 'I AM DONE GRADING' after the task is complete`,
|
||||
})
|
||||
```
|
||||
2370
e2e/package-lock.json
generated
|
|
@ -1,21 +1,24 @@
|
|||
{
|
||||
"name": "e2e",
|
||||
"version": "0.1.0",
|
||||
"name": "@roo-code/vscode-e2e",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint src/**/*.ts --max-warnings=0",
|
||||
"check-types": "tsc --noEmit",
|
||||
"test": "npm run build && npx dotenvx run -f .env.local -- node ./out/runTest.js",
|
||||
"ci": "npm run vscode-test && npm run test",
|
||||
"build": "rimraf out && tsc -p tsconfig.json",
|
||||
"vscode-test": "cd .. && npm run vscode-test"
|
||||
"format": "prettier --write src",
|
||||
"test:ci": "pnpm --filter roo-cline build:development && pnpm test:run",
|
||||
"test:run": "rimraf out && tsc -p tsconfig.json && npx dotenvx run -f .env.local -- node ./out/runTest.js",
|
||||
"clean": "rimraf out .turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@roo-code/types": "^1.12.0",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^22.14.1",
|
||||
"@types/vscode": "^1.95.0",
|
||||
"@vscode/test-cli": "^0.0.10",
|
||||
"@vscode/test-electron": "^2.4.0",
|
||||
"glob": "^11.0.1",
|
||||
"mocha": "^11.1.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ async function main() {
|
|||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
// Passed to `--extensionDevelopmentPath`
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, "../../")
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, "../../src")
|
||||
|
||||
// The path to the extension test script
|
||||
// Passed to --extensionTestsPath
|
||||
|
|
|
|||
228
esbuild.js
|
|
@ -1,228 +0,0 @@
|
|||
const esbuild = require("esbuild")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
const production = process.argv.includes("--production")
|
||||
const watch = process.argv.includes("--watch")
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').Plugin}
|
||||
*/
|
||||
const esbuildProblemMatcherPlugin = {
|
||||
name: "esbuild-problem-matcher",
|
||||
|
||||
setup(build) {
|
||||
build.onStart(() => {
|
||||
console.log("[watch] build started")
|
||||
})
|
||||
build.onEnd((result) => {
|
||||
result.errors.forEach(({ text, location }) => {
|
||||
console.error(`✘ [ERROR] ${text}`)
|
||||
console.error(` ${location.file}:${location.line}:${location.column}:`)
|
||||
})
|
||||
console.log("[watch] build finished")
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const copyWasmFiles = {
|
||||
name: "copy-wasm-files",
|
||||
setup(build) {
|
||||
build.onEnd(() => {
|
||||
const nodeModulesDir = path.join(__dirname, "node_modules")
|
||||
const distDir = path.join(__dirname, "dist")
|
||||
|
||||
// tiktoken WASM file
|
||||
fs.copyFileSync(
|
||||
path.join(nodeModulesDir, "tiktoken", "lite", "tiktoken_bg.wasm"),
|
||||
path.join(distDir, "tiktoken_bg.wasm"),
|
||||
)
|
||||
|
||||
// Also copy to the workers directory
|
||||
fs.mkdirSync(path.join(distDir, "workers"), { recursive: true })
|
||||
fs.copyFileSync(
|
||||
path.join(nodeModulesDir, "tiktoken", "lite", "tiktoken_bg.wasm"),
|
||||
path.join(distDir, "workers", "tiktoken_bg.wasm"),
|
||||
)
|
||||
|
||||
// Main tree-sitter WASM file
|
||||
fs.copyFileSync(
|
||||
path.join(nodeModulesDir, "web-tree-sitter", "tree-sitter.wasm"),
|
||||
path.join(distDir, "tree-sitter.wasm"),
|
||||
)
|
||||
|
||||
// Copy language-specific WASM files
|
||||
const languageWasmDir = path.join(__dirname, "node_modules", "tree-sitter-wasms", "out")
|
||||
|
||||
// Dynamically read all WASM files from the directory instead of using a hardcoded list
|
||||
if (fs.existsSync(languageWasmDir)) {
|
||||
const wasmFiles = fs.readdirSync(languageWasmDir).filter((file) => file.endsWith(".wasm"))
|
||||
|
||||
console.log(`Copying ${wasmFiles.length} tree-sitter WASM files to dist directory`)
|
||||
|
||||
wasmFiles.forEach((filename) => {
|
||||
fs.copyFileSync(path.join(languageWasmDir, filename), path.join(distDir, filename))
|
||||
})
|
||||
} else {
|
||||
console.warn(`Tree-sitter WASM directory not found: ${languageWasmDir}`)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// Simple function to copy locale files
|
||||
function copyLocaleFiles() {
|
||||
const srcDir = path.join(__dirname, "src", "i18n", "locales")
|
||||
const destDir = path.join(__dirname, "dist", "i18n", "locales")
|
||||
const outDir = path.join(__dirname, "out", "i18n", "locales")
|
||||
|
||||
// Ensure source directory exists before proceeding
|
||||
if (!fs.existsSync(srcDir)) {
|
||||
console.warn(`Source locales directory does not exist: ${srcDir}`)
|
||||
return // Exit early if source directory doesn't exist
|
||||
}
|
||||
|
||||
// Create destination directories
|
||||
fs.mkdirSync(destDir, { recursive: true })
|
||||
try {
|
||||
fs.mkdirSync(outDir, { recursive: true })
|
||||
} catch (e) {}
|
||||
|
||||
// Function to copy directory recursively
|
||||
function copyDir(src, dest) {
|
||||
const entries = fs.readdirSync(src, { withFileTypes: true })
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name)
|
||||
const destPath = path.join(dest, entry.name)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Create directory and copy contents
|
||||
fs.mkdirSync(destPath, { recursive: true })
|
||||
copyDir(srcPath, destPath)
|
||||
} else {
|
||||
// Copy the file
|
||||
fs.copyFileSync(srcPath, destPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy files to dist directory
|
||||
copyDir(srcDir, destDir)
|
||||
console.log("Copied locale files to dist/i18n/locales")
|
||||
|
||||
// Copy to out directory for debugging
|
||||
try {
|
||||
copyDir(srcDir, outDir)
|
||||
console.log("Copied locale files to out/i18n/locales")
|
||||
} catch (e) {
|
||||
console.warn("Could not copy to out directory:", e.message)
|
||||
}
|
||||
}
|
||||
|
||||
// Set up file watcher if in watch mode
|
||||
function setupLocaleWatcher() {
|
||||
if (!watch) return
|
||||
|
||||
const localesDir = path.join(__dirname, "src", "i18n", "locales")
|
||||
|
||||
// Ensure the locales directory exists before setting up watcher
|
||||
if (!fs.existsSync(localesDir)) {
|
||||
console.warn(`Cannot set up watcher: Source locales directory does not exist: ${localesDir}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Setting up watcher for locale files in ${localesDir}`)
|
||||
|
||||
// Use a debounce mechanism
|
||||
let debounceTimer = null
|
||||
const debouncedCopy = () => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
debounceTimer = setTimeout(() => {
|
||||
console.log("Locale files changed, copying...")
|
||||
copyLocaleFiles()
|
||||
}, 300) // Wait 300ms after last change before copying
|
||||
}
|
||||
|
||||
// Watch the locales directory
|
||||
try {
|
||||
fs.watch(localesDir, { recursive: true }, (eventType, filename) => {
|
||||
if (filename && filename.endsWith(".json")) {
|
||||
console.log(`Locale file ${filename} changed, triggering copy...`)
|
||||
debouncedCopy()
|
||||
}
|
||||
})
|
||||
console.log("Watcher for locale files is set up")
|
||||
} catch (error) {
|
||||
console.error(`Error setting up watcher for ${localesDir}:`, error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const copyLocalesFiles = {
|
||||
name: "copy-locales-files",
|
||||
setup(build) {
|
||||
build.onEnd(() => {
|
||||
copyLocaleFiles()
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const extensionConfig = {
|
||||
bundle: true,
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
logLevel: "silent",
|
||||
plugins: [
|
||||
copyWasmFiles,
|
||||
copyLocalesFiles,
|
||||
/* add to the end of plugins array */
|
||||
esbuildProblemMatcherPlugin,
|
||||
{
|
||||
name: "alias-plugin",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^pkce-challenge$/ }, (_args) => {
|
||||
return { path: require.resolve("pkce-challenge/dist/index.browser.js") }
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
entryPoints: ["src/extension.ts"],
|
||||
format: "cjs",
|
||||
sourcesContent: false,
|
||||
platform: "node",
|
||||
outfile: "dist/extension.js",
|
||||
external: ["vscode"],
|
||||
}
|
||||
|
||||
const workerConfig = {
|
||||
bundle: true,
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
logLevel: "silent",
|
||||
entryPoints: ["src/workers/countTokens.ts"],
|
||||
format: "cjs",
|
||||
sourcesContent: false,
|
||||
platform: "node",
|
||||
outdir: "dist/workers",
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const [extensionCtx, workerCtx] = await Promise.all([
|
||||
esbuild.context(extensionConfig),
|
||||
esbuild.context(workerConfig),
|
||||
])
|
||||
|
||||
if (watch) {
|
||||
await Promise.all([extensionCtx.watch(), workerCtx.watch()])
|
||||
copyLocaleFiles()
|
||||
setupLocaleWatcher()
|
||||
} else {
|
||||
await Promise.all([extensionCtx.rebuild(), workerCtx.rebuild()])
|
||||
await Promise.all([extensionCtx.dispose(), workerCtx.dispose()])
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@evals/monorepo",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808",
|
||||
"packageManager": "pnpm@10.8.1",
|
||||
"scripts": {
|
||||
"lint": "turbo lint --log-order grouped --output-logs new-only",
|
||||
"check-types": "turbo check-types --log-order grouped --output-logs new-only",
|
||||
|
|
|
|||
3224
evals/pnpm-lock.yaml
generated
|
|
@ -28,11 +28,8 @@ build_extension() {
|
|||
echo "🔨 Building the Roo Code extension..."
|
||||
cd ..
|
||||
mkdir -p bin
|
||||
npm run install-extension -- --silent --no-audit || exit 1
|
||||
npm run install-webview -- --silent --no-audit || exit 1
|
||||
npm run install-e2e -- --silent --no-audit || exit 1
|
||||
npx vsce package --out bin/roo-code-latest.vsix || exit 1
|
||||
code --install-extension bin/roo-code-latest.vsix || exit 1
|
||||
pnpm build --out ../bin/roo-code-$(git rev-parse --short HEAD).vsix || exit 1
|
||||
code --install-extension bin/roo-code-$(git rev-parse --short HEAD).vsix || exit 1
|
||||
cd evals
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@
|
|||
"**/*.test.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/stories/**",
|
||||
"coverage/**",
|
||||
"dist/**",
|
||||
"out/**",
|
||||
"bin/**",
|
||||
"e2e/**",
|
||||
"evals/**",
|
||||
|
|
@ -20,7 +17,8 @@
|
|||
"src/workers/**",
|
||||
"src/schemas/ipc.ts",
|
||||
"src/extension.ts",
|
||||
"scripts/**"
|
||||
"scripts/**",
|
||||
"vitest.config.ts"
|
||||
],
|
||||
"workspaces": {
|
||||
"webview-ui": {
|
||||
|
|
|
|||
20885
package-lock.json
generated
468
package.json
|
|
@ -1,479 +1,55 @@
|
|||
{
|
||||
"name": "roo-cline",
|
||||
"displayName": "%extension.displayName%",
|
||||
"description": "%extension.description%",
|
||||
"publisher": "RooVeterinaryInc",
|
||||
"version": "3.18.0",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"galleryBanner": {
|
||||
"color": "#617A91",
|
||||
"theme": "dark"
|
||||
},
|
||||
"name": "roo-code",
|
||||
"packageManager": "pnpm@10.8.1",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0",
|
||||
"node": "20.18.1"
|
||||
},
|
||||
"author": {
|
||||
"name": "Roo Code"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/RooCodeInc/Roo-Code"
|
||||
},
|
||||
"homepage": "https://github.com/RooCodeInc/Roo-Code",
|
||||
"categories": [
|
||||
"AI",
|
||||
"Chat",
|
||||
"Programming Languages",
|
||||
"Education",
|
||||
"Snippets",
|
||||
"Testing"
|
||||
],
|
||||
"keywords": [
|
||||
"cline",
|
||||
"claude",
|
||||
"dev",
|
||||
"mcp",
|
||||
"openrouter",
|
||||
"coding",
|
||||
"agent",
|
||||
"autonomous",
|
||||
"chatgpt",
|
||||
"sonnet",
|
||||
"ai",
|
||||
"llama",
|
||||
"roo code",
|
||||
"roocode"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage",
|
||||
"onStartupFinished"
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "roo-cline-ActivityBar",
|
||||
"title": "%views.activitybar.title%",
|
||||
"icon": "assets/icons/icon.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"roo-cline-ActivityBar": [
|
||||
{
|
||||
"type": "webview",
|
||||
"id": "roo-cline.SidebarProvider",
|
||||
"name": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "roo-cline.plusButtonClicked",
|
||||
"title": "%command.newTask.title%",
|
||||
"icon": "$(add)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.mcpButtonClicked",
|
||||
"title": "%command.mcpServers.title%",
|
||||
"icon": "$(server)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.promptsButtonClicked",
|
||||
"title": "%command.prompts.title%",
|
||||
"icon": "$(notebook)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.historyButtonClicked",
|
||||
"title": "%command.history.title%",
|
||||
"icon": "$(history)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.popoutButtonClicked",
|
||||
"title": "%command.openInEditor.title%",
|
||||
"icon": "$(link-external)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.settingsButtonClicked",
|
||||
"title": "%command.settings.title%",
|
||||
"icon": "$(settings-gear)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.openInNewTab",
|
||||
"title": "%command.openInNewTab.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.explainCode",
|
||||
"title": "%command.explainCode.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.fixCode",
|
||||
"title": "%command.fixCode.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.improveCode",
|
||||
"title": "%command.improveCode.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.addToContext",
|
||||
"title": "%command.addToContext.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.newTask",
|
||||
"title": "%command.newTask.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalAddToContext",
|
||||
"title": "%command.terminal.addToContext.title%",
|
||||
"category": "Terminal"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalFixCommand",
|
||||
"title": "%command.terminal.fixCommand.title%",
|
||||
"category": "Terminal"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalExplainCommand",
|
||||
"title": "%command.terminal.explainCommand.title%",
|
||||
"category": "Terminal"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.setCustomStoragePath",
|
||||
"title": "%command.setCustomStoragePath.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.focusInput",
|
||||
"title": "%command.focusInput.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.acceptInput",
|
||||
"title": "%command.acceptInput.title%",
|
||||
"category": "%configuration.title%"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"editor/context": [
|
||||
{
|
||||
"submenu": "roo-cline.contextMenu",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"roo-cline.contextMenu": [
|
||||
{
|
||||
"command": "roo-cline.addToContext",
|
||||
"group": "1_actions@1"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.explainCode",
|
||||
"group": "1_actions@2"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.improveCode",
|
||||
"group": "1_actions@3"
|
||||
}
|
||||
],
|
||||
"terminal/context": [
|
||||
{
|
||||
"submenu": "roo-cline.terminalMenu",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"roo-cline.terminalMenu": [
|
||||
{
|
||||
"command": "roo-cline.terminalAddToContext",
|
||||
"group": "1_actions@1"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalFixCommand",
|
||||
"group": "1_actions@2"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalExplainCommand",
|
||||
"group": "1_actions@3"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "roo-cline.plusButtonClicked",
|
||||
"group": "navigation@1",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.promptsButtonClicked",
|
||||
"group": "navigation@2",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.mcpButtonClicked",
|
||||
"group": "navigation@3",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.historyButtonClicked",
|
||||
"group": "navigation@4",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.popoutButtonClicked",
|
||||
"group": "navigation@5",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.settingsButtonClicked",
|
||||
"group": "navigation@6",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "roo-cline.plusButtonClicked",
|
||||
"group": "navigation@1",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.promptsButtonClicked",
|
||||
"group": "navigation@2",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.mcpButtonClicked",
|
||||
"group": "navigation@3",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.historyButtonClicked",
|
||||
"group": "navigation@4",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.popoutButtonClicked",
|
||||
"group": "navigation@5",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.settingsButtonClicked",
|
||||
"group": "navigation@6",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
}
|
||||
]
|
||||
},
|
||||
"submenus": [
|
||||
{
|
||||
"id": "roo-cline.contextMenu",
|
||||
"label": "%views.contextMenu.label%"
|
||||
},
|
||||
{
|
||||
"id": "roo-cline.terminalMenu",
|
||||
"label": "%views.terminalMenu.label%"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "%configuration.title%",
|
||||
"properties": {
|
||||
"roo-cline.allowedCommands": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"npm test",
|
||||
"npm install",
|
||||
"tsc",
|
||||
"git log",
|
||||
"git diff",
|
||||
"git show"
|
||||
],
|
||||
"description": "%commands.allowedCommands.description%"
|
||||
},
|
||||
"roo-cline.vsCodeLmModelSelector": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"description": "%settings.vsCodeLmModelSelector.vendor.description%"
|
||||
},
|
||||
"family": {
|
||||
"type": "string",
|
||||
"description": "%settings.vsCodeLmModelSelector.family.description%"
|
||||
}
|
||||
},
|
||||
"description": "%settings.vsCodeLmModelSelector.description%"
|
||||
},
|
||||
"roo-cline.customStoragePath": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "%settings.customStoragePath.description%"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run vsix",
|
||||
"build:webview": "cd webview-ui && npm run build",
|
||||
"build:esbuild": "node esbuild.js --production",
|
||||
"compile": "tsc -p . --outDir out && node esbuild.js",
|
||||
"install:all": "npm install -D npm-run-all2@8.0.1 && npm-run-all -l -p install-*",
|
||||
"install-extension": "npm install",
|
||||
"install-webview": "cd webview-ui && npm install",
|
||||
"install-e2e": "cd e2e && npm install",
|
||||
"lint": "npm-run-all -l -p lint:*",
|
||||
"lint:extension": "eslint src --ext .ts",
|
||||
"lint:webview": "cd webview-ui && npm run lint",
|
||||
"lint:e2e": "cd e2e && npm run lint",
|
||||
"check-types": "npm-run-all -l -p check-types:*",
|
||||
"check-types:extension": "tsc --noEmit",
|
||||
"check-types:webview": "cd webview-ui && npm run check-types",
|
||||
"check-types:e2e": "cd e2e && npm run check-types",
|
||||
"package": "npm-run-all -l -p build:webview build:esbuild check-types lint",
|
||||
"pretest": "npm run compile",
|
||||
"dev": "cd webview-ui && npm run dev",
|
||||
"test": "npm-run-all test:*",
|
||||
"test:extension": "jest -w=40%",
|
||||
"test:extension-esm": "vitest run",
|
||||
"test:webview": "cd webview-ui && npm run test",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepare": "husky",
|
||||
"publish:marketplace": "npx vsce publish && npx ovsx publish",
|
||||
"publish": "npm run build && changeset publish && npm install --package-lock-only",
|
||||
"version-packages": "changeset version && npm install --package-lock-only",
|
||||
"vscode:prepublish": "npm run package",
|
||||
"vsix": "rimraf bin && mkdirp bin && npx vsce package --out bin",
|
||||
"watch": "npm-run-all -l -p watch:*",
|
||||
"watch:esbuild": "node esbuild.js --watch",
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"watch-tests": "tsc -p . -w --outDir out",
|
||||
"lint": "turbo lint --log-order grouped --output-logs new-only",
|
||||
"check-types": "turbo check-types --log-order grouped --output-logs new-only",
|
||||
"test": "turbo test --log-order grouped --output-logs new-only",
|
||||
"format": "turbo format --log-order grouped --output-logs new-only",
|
||||
"clean": "turbo clean --log-order grouped --output-logs new-only && rimraf dist out bin .vite-port .turbo",
|
||||
"build": "pnpm --filter roo-cline vsix",
|
||||
"build:nightly": "pnpm --filter @roo-code/vscode-nightly vsix",
|
||||
"changeset": "changeset",
|
||||
"knip": "knip --include files",
|
||||
"clean": "npm-run-all -l -p clean:*",
|
||||
"clean:extension": "rimraf bin dist out",
|
||||
"clean:webview": "cd webview-ui && npm run clean",
|
||||
"clean:e2e": "cd e2e && npm run clean",
|
||||
"vscode-test": "npm-run-all -l -p vscode-test:*",
|
||||
"vscode-test:extension": "tsc -p . --outDir out && node esbuild.js",
|
||||
"vscode-test:webview": "cd webview-ui && npm run build",
|
||||
"update-contributors": "node scripts/update-contributors.js",
|
||||
"generate-types": "tsx scripts/generate-types.mts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/bedrock-sdk": "^0.10.2",
|
||||
"@anthropic-ai/sdk": "^0.37.0",
|
||||
"@anthropic-ai/vertex-sdk": "^0.7.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.779.0",
|
||||
"@google/genai": "^0.13.0",
|
||||
"@mistralai/mistralai": "^1.3.6",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"@types/clone-deep": "^4.0.4",
|
||||
"@types/pdf-parse": "^1.1.4",
|
||||
"@types/tmp": "^0.2.6",
|
||||
"@types/turndown": "^5.0.5",
|
||||
"@types/vscode": "^1.95.0",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"axios": "^1.7.4",
|
||||
"cheerio": "^1.0.0",
|
||||
"chokidar": "^4.0.1",
|
||||
"clone-deep": "^4.0.1",
|
||||
"default-shell": "^2.2.0",
|
||||
"delay": "^6.0.0",
|
||||
"diff": "^5.2.0",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-xml-parser": "^4.5.1",
|
||||
"fastest-levenshtein": "^1.0.16",
|
||||
"fzf": "^0.5.2",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"i18next": "^24.2.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"mammoth": "^1.8.0",
|
||||
"monaco-vscode-textmate-theme-converter": "^0.1.7",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-ipc": "^12.0.0",
|
||||
"openai": "^4.78.1",
|
||||
"os-name": "^6.0.0",
|
||||
"p-wait-for": "^5.0.2",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"pkce-challenge": "^4.1.0",
|
||||
"posthog-node": "^4.7.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"ps-tree": "^1.2.0",
|
||||
"puppeteer-chromium-resolver": "^23.0.0",
|
||||
"puppeteer-core": "^23.4.0",
|
||||
"reconnecting-eventsource": "^1.6.4",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"say": "^0.16.0",
|
||||
"serialize-error": "^11.0.3",
|
||||
"simple-git": "^3.27.0",
|
||||
"string-similarity": "^4.0.4",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"strip-bom": "^5.0.0",
|
||||
"tiktoken": "^1.0.21",
|
||||
"tmp": "^0.2.3",
|
||||
"tree-sitter-wasms": "^0.1.11",
|
||||
"turndown": "^7.2.0",
|
||||
"vscode-material-icons": "^0.1.1",
|
||||
"web-tree-sitter": "^0.22.6",
|
||||
"workerpool": "^9.2.0",
|
||||
"yaml": "^2.8.0",
|
||||
"zod": "^3.24.2"
|
||||
"knip": "pnpm --filter @roo-code/build build && knip --include files",
|
||||
"update-contributors": "node scripts/update-contributors.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.27.10",
|
||||
"@changesets/types": "^6.0.0",
|
||||
"@dotenvx/dotenvx": "^1.34.0",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/diff": "^5.2.1",
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "20.x",
|
||||
"@types/node-cache": "^4.1.3",
|
||||
"@types/node-ipc": "^9.2.3",
|
||||
"@types/ps-tree": "^1.1.6",
|
||||
"@types/string-similarity": "^4.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"@vscode/vsce": "3.3.2",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^8.57.0",
|
||||
"execa": "^9.5.2",
|
||||
"glob": "^11.0.1",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.7.0",
|
||||
"jest-simple-dot-reporter": "^1.0.5",
|
||||
"knip": "^5.44.4",
|
||||
"lint-staged": "^15.2.11",
|
||||
"mkdirp": "^3.0.1",
|
||||
"nock": "^14.0.4",
|
||||
"npm-run-all2": "^8.0.1",
|
||||
"only-allow": "^1.2.1",
|
||||
"ovsx": "0.10.2",
|
||||
"prettier": "^3.4.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-jest": "^29.2.5",
|
||||
"tsup": "^8.4.0",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "5.8.3",
|
||||
"vitest": "^3.1.3",
|
||||
"zod-to-ts": "^1.2.0"
|
||||
"turbo": "^2.5.3",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,json,css,md}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"src/**/*.{ts,tsx}": [
|
||||
"npx eslint -c .eslintrc.json --max-warnings=0 --fix"
|
||||
"npx eslint -c src/.eslintrc.json --max-warnings=0 --fix"
|
||||
],
|
||||
"webview-ui/**/*.{ts,tsx}": [
|
||||
"npx eslint -c webview-ui/.eslintrc.json --max-warnings=0 --fix"
|
||||
],
|
||||
"e2e/**/*.{ts,tsx}": [
|
||||
"npx eslint -c src/.eslintrc.json --max-warnings=0 --fix"
|
||||
],
|
||||
"{apps,packages}/**/*.{ts,tsx}": [
|
||||
"npx eslint --max-warnings=0 --fix"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
packages/build/.eslintrc.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{
|
||||
"selector": "import",
|
||||
"format": ["camelCase", "PascalCase"]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }],
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off"
|
||||
},
|
||||
"ignorePatterns": ["dist"]
|
||||
}
|
||||
21
packages/build/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "@roo-code/build",
|
||||
"description": "ESBuild utilities for Roo Code.",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint src --ext .ts,.tsx --max-warnings=0",
|
||||
"check-types": "tsc --noEmit",
|
||||
"test": "vitest --globals --run",
|
||||
"build": "tsc",
|
||||
"clean": "rimraf dist .turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.15.20",
|
||||
"vitest": "^3.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.24.2"
|
||||
}
|
||||
}
|
||||
212
packages/build/src/__tests__/index.test.ts
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
// npx vitest --globals run src/__tests__/index.test.ts
|
||||
|
||||
import { generatePackageJson } from "../index.js"
|
||||
|
||||
describe("generatePackageJson", () => {
|
||||
it("should be a test", () => {
|
||||
const generatedPackageJson = generatePackageJson({
|
||||
packageJson: {
|
||||
name: "roo-cline",
|
||||
displayName: "%extension.displayName%",
|
||||
description: "%extension.description%",
|
||||
publisher: "RooVeterinaryInc",
|
||||
version: "3.17.2",
|
||||
icon: "assets/icons/icon.png",
|
||||
contributes: {
|
||||
viewsContainers: {
|
||||
activitybar: [
|
||||
{
|
||||
id: "roo-cline-ActivityBar",
|
||||
title: "%views.activitybar.title%",
|
||||
icon: "assets/icons/icon.svg",
|
||||
},
|
||||
],
|
||||
},
|
||||
views: {
|
||||
"roo-cline-ActivityBar": [
|
||||
{
|
||||
type: "webview",
|
||||
id: "roo-cline.SidebarProvider",
|
||||
name: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
commands: [
|
||||
{
|
||||
command: "roo-cline.plusButtonClicked",
|
||||
title: "%command.newTask.title%",
|
||||
icon: "$(add)",
|
||||
},
|
||||
{
|
||||
command: "roo-cline.openInNewTab",
|
||||
title: "%command.openInNewTab.title%",
|
||||
category: "%configuration.title%",
|
||||
},
|
||||
],
|
||||
menus: {
|
||||
"editor/context": [
|
||||
{
|
||||
submenu: "roo-cline.contextMenu",
|
||||
group: "navigation",
|
||||
},
|
||||
],
|
||||
"roo-cline.contextMenu": [
|
||||
{
|
||||
command: "roo-cline.addToContext",
|
||||
group: "1_actions@1",
|
||||
},
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
command: "roo-cline.plusButtonClicked",
|
||||
group: "navigation@1",
|
||||
when: "activeWebviewPanelId == roo-cline.TabPanelProvider",
|
||||
},
|
||||
{
|
||||
command: "roo-cline.settingsButtonClicked",
|
||||
group: "navigation@6",
|
||||
when: "activeWebviewPanelId == roo-cline.TabPanelProvider",
|
||||
},
|
||||
],
|
||||
},
|
||||
submenus: [
|
||||
{
|
||||
id: "roo-cline.contextMenu",
|
||||
label: "%views.contextMenu.label%",
|
||||
},
|
||||
{
|
||||
id: "roo-cline.terminalMenu",
|
||||
label: "%views.terminalMenu.label%",
|
||||
},
|
||||
],
|
||||
configuration: {
|
||||
title: "%configuration.title%",
|
||||
properties: {
|
||||
"roo-cline.allowedCommands": {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
default: ["npm test", "npm install", "tsc", "git log", "git diff", "git show"],
|
||||
description: "%commands.allowedCommands.description%",
|
||||
},
|
||||
"roo-cline.customStoragePath": {
|
||||
type: "string",
|
||||
default: "",
|
||||
description: "%settings.customStoragePath.description%",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
scripts: {
|
||||
lint: "eslint **/*.ts",
|
||||
},
|
||||
},
|
||||
overrideJson: {
|
||||
name: "roo-code-nightly",
|
||||
displayName: "Roo Code Nightly",
|
||||
publisher: "RooVeterinaryInc",
|
||||
version: "0.0.1",
|
||||
icon: "assets/icons/icon-nightly.png",
|
||||
scripts: {},
|
||||
},
|
||||
substitution: ["roo-cline", "roo-code-nightly"],
|
||||
})
|
||||
|
||||
expect(generatedPackageJson).toStrictEqual({
|
||||
name: "roo-code-nightly",
|
||||
displayName: "Roo Code Nightly",
|
||||
description: "%extension.description%",
|
||||
publisher: "RooVeterinaryInc",
|
||||
version: "0.0.1",
|
||||
icon: "assets/icons/icon-nightly.png",
|
||||
contributes: {
|
||||
viewsContainers: {
|
||||
activitybar: [
|
||||
{
|
||||
id: "roo-code-nightly-ActivityBar",
|
||||
title: "%views.activitybar.title%",
|
||||
icon: "assets/icons/icon.svg",
|
||||
},
|
||||
],
|
||||
},
|
||||
views: {
|
||||
"roo-code-nightly-ActivityBar": [
|
||||
{
|
||||
type: "webview",
|
||||
id: "roo-code-nightly.SidebarProvider",
|
||||
name: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
commands: [
|
||||
{
|
||||
command: "roo-code-nightly.plusButtonClicked",
|
||||
title: "%command.newTask.title%",
|
||||
icon: "$(add)",
|
||||
},
|
||||
{
|
||||
command: "roo-code-nightly.openInNewTab",
|
||||
title: "%command.openInNewTab.title%",
|
||||
category: "%configuration.title%",
|
||||
},
|
||||
],
|
||||
menus: {
|
||||
"editor/context": [
|
||||
{
|
||||
submenu: "roo-code-nightly.contextMenu",
|
||||
group: "navigation",
|
||||
},
|
||||
],
|
||||
"roo-code-nightly.contextMenu": [
|
||||
{
|
||||
command: "roo-code-nightly.addToContext",
|
||||
group: "1_actions@1",
|
||||
},
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
command: "roo-code-nightly.plusButtonClicked",
|
||||
group: "navigation@1",
|
||||
when: "activeWebviewPanelId == roo-code-nightly.TabPanelProvider",
|
||||
},
|
||||
{
|
||||
command: "roo-code-nightly.settingsButtonClicked",
|
||||
group: "navigation@6",
|
||||
when: "activeWebviewPanelId == roo-code-nightly.TabPanelProvider",
|
||||
},
|
||||
],
|
||||
},
|
||||
submenus: [
|
||||
{
|
||||
id: "roo-code-nightly.contextMenu",
|
||||
label: "%views.contextMenu.label%",
|
||||
},
|
||||
{
|
||||
id: "roo-code-nightly.terminalMenu",
|
||||
label: "%views.terminalMenu.label%",
|
||||
},
|
||||
],
|
||||
configuration: {
|
||||
title: "%configuration.title%",
|
||||
properties: {
|
||||
"roo-code-nightly.allowedCommands": {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
default: ["npm test", "npm install", "tsc", "git log", "git diff", "git show"],
|
||||
description: "%commands.allowedCommands.description%",
|
||||
},
|
||||
"roo-code-nightly.customStoragePath": {
|
||||
type: "string",
|
||||
default: "",
|
||||
description: "%settings.customStoragePath.description%",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
scripts: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
205
packages/build/src/esbuild.ts
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
|
||||
import { ViewsContainer, Views, Menus, Configuration, contributesSchema } from "./types.js"
|
||||
|
||||
export function copyPaths(copyPaths: [string, string][], srcDir: string, dstDir: string) {
|
||||
copyPaths.forEach(([srcRelPath, dstRelPath]) => {
|
||||
const stats = fs.lstatSync(path.join(srcDir, srcRelPath))
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
if (fs.existsSync(path.join(dstDir, dstRelPath))) {
|
||||
fs.rmSync(path.join(dstDir, dstRelPath), { recursive: true })
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.join(dstDir, dstRelPath), { recursive: true })
|
||||
|
||||
const count = copyDir(path.join(srcDir, srcRelPath), path.join(dstDir, dstRelPath), 0)
|
||||
console.log(`[copyPaths] Copied ${count} files from ${srcRelPath} to ${dstRelPath}`)
|
||||
} else {
|
||||
fs.copyFileSync(path.join(srcDir, srcRelPath), path.join(dstDir, dstRelPath))
|
||||
console.log(`[copyPaths] Copied ${srcRelPath} to ${dstRelPath}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function copyDir(srcDir: string, dstDir: string, count: number): number {
|
||||
const entries = fs.readdirSync(srcDir, { withFileTypes: true })
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(srcDir, entry.name)
|
||||
const dstPath = path.join(dstDir, entry.name)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
fs.mkdirSync(dstPath, { recursive: true })
|
||||
count = copyDir(srcPath, dstPath, count)
|
||||
} else {
|
||||
count = count + 1
|
||||
fs.copyFileSync(srcPath, dstPath)
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
export function copyWasms(srcDir: string, distDir: string): void {
|
||||
const nodeModulesDir = path.join(srcDir, "node_modules")
|
||||
|
||||
fs.mkdirSync(distDir, { recursive: true })
|
||||
|
||||
// Tiktoken WASM file.
|
||||
fs.copyFileSync(
|
||||
path.join(nodeModulesDir, "tiktoken", "lite", "tiktoken_bg.wasm"),
|
||||
path.join(distDir, "tiktoken_bg.wasm"),
|
||||
)
|
||||
|
||||
console.log(`[copyWasms] Copied tiktoken WASMs to ${distDir}`)
|
||||
|
||||
// Also copy Tiktoken WASMs to the workers directory.
|
||||
const workersDir = path.join(distDir, "workers")
|
||||
fs.mkdirSync(workersDir, { recursive: true })
|
||||
|
||||
fs.copyFileSync(
|
||||
path.join(nodeModulesDir, "tiktoken", "lite", "tiktoken_bg.wasm"),
|
||||
path.join(workersDir, "tiktoken_bg.wasm"),
|
||||
)
|
||||
|
||||
console.log(`[copyWasms] Copied tiktoken WASMs to ${workersDir}`)
|
||||
|
||||
// Main tree-sitter WASM file.
|
||||
fs.copyFileSync(
|
||||
path.join(nodeModulesDir, "web-tree-sitter", "tree-sitter.wasm"),
|
||||
path.join(distDir, "tree-sitter.wasm"),
|
||||
)
|
||||
|
||||
console.log(`[copyWasms] Copied tree-sitter.wasm to ${distDir}`)
|
||||
|
||||
// Copy language-specific WASM files.
|
||||
const languageWasmDir = path.join(nodeModulesDir, "tree-sitter-wasms", "out")
|
||||
|
||||
if (!fs.existsSync(languageWasmDir)) {
|
||||
throw new Error(`Directory does not exist: ${languageWasmDir}`)
|
||||
}
|
||||
|
||||
// Dynamically read all WASM files from the directory instead of using a hardcoded list.
|
||||
const wasmFiles = fs.readdirSync(languageWasmDir).filter((file) => file.endsWith(".wasm"))
|
||||
|
||||
wasmFiles.forEach((filename) => {
|
||||
fs.copyFileSync(path.join(languageWasmDir, filename), path.join(distDir, filename))
|
||||
})
|
||||
|
||||
console.log(`[copyWasms] Copied ${wasmFiles.length} tree-sitter language wasms to ${distDir}`)
|
||||
}
|
||||
|
||||
export function copyLocales(srcDir: string, distDir: string): void {
|
||||
const destDir = path.join(distDir, "i18n", "locales")
|
||||
fs.mkdirSync(destDir, { recursive: true })
|
||||
const count = copyDir(path.join(srcDir, "i18n", "locales"), destDir, 0)
|
||||
console.log(`[copyLocales] Copied ${count} locale files to ${destDir}`)
|
||||
}
|
||||
|
||||
export function setupLocaleWatcher(srcDir: string, distDir: string) {
|
||||
const localesDir = path.join(srcDir, "i18n", "locales")
|
||||
|
||||
if (!fs.existsSync(localesDir)) {
|
||||
console.warn(`Cannot set up watcher: Source locales directory does not exist: ${localesDir}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Setting up watcher for locale files in ${localesDir}`)
|
||||
|
||||
let debounceTimer: NodeJS.Timeout | null = null
|
||||
|
||||
const debouncedCopy = () => {
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer)
|
||||
}
|
||||
|
||||
// Wait 300ms after last change before copying.
|
||||
debounceTimer = setTimeout(() => {
|
||||
console.log("Locale files changed, copying...")
|
||||
copyLocales(srcDir, distDir)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
try {
|
||||
fs.watch(localesDir, { recursive: true }, (_eventType, filename) => {
|
||||
if (filename && filename.endsWith(".json")) {
|
||||
console.log(`Locale file ${filename} changed, triggering copy...`)
|
||||
debouncedCopy()
|
||||
}
|
||||
})
|
||||
console.log("Watcher for locale files is set up")
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error setting up watcher for ${localesDir}:`,
|
||||
error instanceof Error ? error.message : "Unknown error",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function generatePackageJson({
|
||||
packageJson: { contributes, ...packageJson },
|
||||
overrideJson,
|
||||
substitution,
|
||||
}: {
|
||||
packageJson: Record<string, any>
|
||||
overrideJson: Record<string, any>
|
||||
substitution: [string, string]
|
||||
}) {
|
||||
const { viewsContainers, views, commands, menus, submenus, configuration } = contributesSchema.parse(contributes)
|
||||
const [from, to] = substitution
|
||||
|
||||
return {
|
||||
...packageJson,
|
||||
...overrideJson,
|
||||
contributes: {
|
||||
viewsContainers: transformArrayRecord<ViewsContainer>(viewsContainers, from, to, ["id"]),
|
||||
views: transformArrayRecord<Views>(views, from, to, ["id"]),
|
||||
commands: transformArray(commands, from, to, "command"),
|
||||
menus: transformArrayRecord<Menus>(menus, from, to, ["command", "submenu", "when"]),
|
||||
submenus: transformArray(submenus, from, to, "id"),
|
||||
configuration: {
|
||||
title: configuration.title,
|
||||
properties: transformRecord<Configuration["properties"]>(configuration.properties, from, to),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function transformArrayRecord<T>(obj: Record<string, any[]>, from: string, to: string, props: string[]): T {
|
||||
return Object.entries(obj).reduce(
|
||||
(acc, [key, ary]) => ({
|
||||
...acc,
|
||||
[key.replace(from, to)]: ary.map((item) => {
|
||||
const transformedItem = { ...item }
|
||||
|
||||
for (const prop of props) {
|
||||
if (prop in item && typeof item[prop] === "string") {
|
||||
transformedItem[prop] = item[prop].replace(from, to)
|
||||
}
|
||||
}
|
||||
|
||||
return transformedItem
|
||||
}),
|
||||
}),
|
||||
{} as T,
|
||||
)
|
||||
}
|
||||
|
||||
function transformArray<T>(arr: any[], from: string, to: string, idProp: string): T[] {
|
||||
return arr.map(({ [idProp]: id, ...rest }) => ({
|
||||
[idProp]: id.replace(from, to),
|
||||
...rest,
|
||||
}))
|
||||
}
|
||||
|
||||
function transformRecord<T>(obj: Record<string, any>, from: string, to: string): T {
|
||||
return Object.entries(obj).reduce(
|
||||
(acc, [key, value]) => ({
|
||||
...acc,
|
||||
[key.replace(from, to)]: value,
|
||||
}),
|
||||
{} as T,
|
||||
)
|
||||
}
|
||||
11
packages/build/src/git.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { execSync } from "child_process"
|
||||
|
||||
export function getGitSha() {
|
||||
let gitSha = undefined
|
||||
|
||||
try {
|
||||
gitSha = execSync("git rev-parse HEAD").toString().trim()
|
||||
} catch (e) {}
|
||||
|
||||
return gitSha
|
||||
}
|
||||
2
packages/build/src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { getGitSha } from "./git.js"
|
||||
export { copyPaths, copyDir, copyWasms, copyLocales, setupLocaleWatcher, generatePackageJson } from "./esbuild.js"
|
||||
98
packages/build/src/types.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { z } from "zod"
|
||||
|
||||
const viewsContainerSchema = z.record(
|
||||
z.string(),
|
||||
z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
icon: z.string(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
export type ViewsContainer = z.infer<typeof viewsContainerSchema>
|
||||
|
||||
const viewsSchema = z.record(
|
||||
z.string(),
|
||||
z.array(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
export type Views = z.infer<typeof viewsSchema>
|
||||
|
||||
const commandsSchema = z.array(
|
||||
z.object({
|
||||
command: z.string(),
|
||||
title: z.string(),
|
||||
category: z.string().optional(),
|
||||
icon: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
|
||||
export type Commands = z.infer<typeof commandsSchema>
|
||||
|
||||
const menuItemSchema = z.object({
|
||||
group: z.string(),
|
||||
command: z.string().optional(),
|
||||
submenu: z.string().optional(),
|
||||
when: z.string().optional(),
|
||||
})
|
||||
|
||||
export type MenuItem = z.infer<typeof menuItemSchema>
|
||||
|
||||
const menusSchema = z.record(z.string(), z.array(menuItemSchema))
|
||||
|
||||
export type Menus = z.infer<typeof menusSchema>
|
||||
|
||||
const submenusSchema = z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
label: z.string(),
|
||||
}),
|
||||
)
|
||||
|
||||
export type Submenus = z.infer<typeof submenusSchema>
|
||||
|
||||
const configurationPropertySchema = z.object({
|
||||
type: z.union([
|
||||
z.literal("string"),
|
||||
z.literal("array"),
|
||||
z.literal("object"),
|
||||
z.literal("boolean"),
|
||||
z.literal("number"),
|
||||
]),
|
||||
items: z
|
||||
.object({
|
||||
type: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
properties: z.record(z.string(), z.any()).optional(),
|
||||
default: z.any().optional(),
|
||||
description: z.string(),
|
||||
})
|
||||
|
||||
export type ConfigurationProperty = z.infer<typeof configurationPropertySchema>
|
||||
|
||||
const configurationSchema = z.object({
|
||||
title: z.string(),
|
||||
properties: z.record(z.string(), configurationPropertySchema),
|
||||
})
|
||||
|
||||
export type Configuration = z.infer<typeof configurationSchema>
|
||||
|
||||
export const contributesSchema = z.object({
|
||||
viewsContainers: viewsContainerSchema,
|
||||
views: viewsSchema,
|
||||
commands: commandsSchema,
|
||||
menus: menusSchema,
|
||||
submenus: submenusSchema,
|
||||
configuration: configurationSchema,
|
||||
})
|
||||
|
||||
export type Contributes = z.infer<typeof contributesSchema>
|
||||
22
packages/build/tsconfig.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"incremental": false,
|
||||
"isolatedModules": true,
|
||||
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
||||
"module": "NodeNext",
|
||||
"moduleDetection": "force",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ES2022",
|
||||
"types": ["vitest/globals"],
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
20586
pnpm-lock.yaml
generated
Normal file
6
pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
packages:
|
||||
- "src"
|
||||
- "webview-ui"
|
||||
- "e2e"
|
||||
- "apps/*"
|
||||
- "packages/*"
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import path from "path"
|
||||
import fs from "fs"
|
||||
|
||||
import { zodToTs, createTypeAlias, printNode } from "zod-to-ts"
|
||||
import { $ } from "execa"
|
||||
|
||||
import schemas from "../src/schemas"
|
||||
|
||||
const { typeDefinitions } = schemas
|
||||
|
||||
async function main() {
|
||||
const types: string[] = [
|
||||
"// This file is automatically generated by running `npm run generate-types`\n// Do not edit it directly.",
|
||||
]
|
||||
|
||||
for (const { schema, identifier } of typeDefinitions) {
|
||||
types.push(printNode(createTypeAlias(zodToTs(schema, identifier).node, identifier)))
|
||||
types.push(`export type { ${identifier} }`)
|
||||
}
|
||||
|
||||
fs.writeFileSync("src/exports/types.ts", types.join("\n\n"))
|
||||
|
||||
await $`npx tsup src/exports/interface.ts --dts -d out`
|
||||
fs.copyFileSync("out/interface.d.ts", "src/exports/roo-code.d.ts")
|
||||
|
||||
await $`npx prettier --write src/exports/types.ts src/exports/roo-code.d.ts`
|
||||
|
||||
if (fs.existsSync(path.join("..", "Roo-Code-Types"))) {
|
||||
fs.copyFileSync("out/interface.js", path.join("..", "Roo-Code-Types", "src", "index.js"))
|
||||
fs.copyFileSync("out/interface.d.ts", path.join("..", "Roo-Code-Types", "src", "index.d.ts"))
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
5
src/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
README.md
|
||||
CHANGELOG.md
|
||||
LICENSE
|
||||
webview-ui
|
||||
assets/vscode-material-icons
|
||||
2
src/.prettierignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
dist/
|
||||
webview-ui/
|
||||
33
src/.vscodeignore
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Exclude everything
|
||||
**
|
||||
|
||||
# Include README.md, CHANGELOG.md and LICENSE
|
||||
!README.md
|
||||
!CHANGELOG.md
|
||||
!LICENSE
|
||||
|
||||
# Include package.json
|
||||
!package.json
|
||||
!package.nls.*
|
||||
|
||||
# Include the built extension
|
||||
!dist
|
||||
|
||||
# Include the built webview
|
||||
**/*.map
|
||||
!webview-ui/audio
|
||||
!webview-ui/build/assets/*.js
|
||||
!webview-ui/build/assets/*.ttf
|
||||
!webview-ui/build/assets/*.css
|
||||
|
||||
# Include default themes JSON files used in getTheme
|
||||
!integrations/theme/default-themes/**
|
||||
|
||||
# Include icons and images
|
||||
!assets/codicons/**
|
||||
!assets/vscode-material-icons/**
|
||||
!assets/icons/**
|
||||
!assets/images/**
|
||||
|
||||
# Include .env file for telemetry
|
||||
!.env
|
||||
1769
src/assets/codicons/codicon.css
Normal file
BIN
src/assets/codicons/codicon.ttf
Normal file
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
BIN
src/assets/icons/icon.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 884 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
|
@ -572,21 +572,8 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
|
|||
"index.css",
|
||||
])
|
||||
|
||||
const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [
|
||||
"node_modules",
|
||||
"@vscode",
|
||||
"codicons",
|
||||
"dist",
|
||||
"codicon.css",
|
||||
])
|
||||
|
||||
const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, [
|
||||
"node_modules",
|
||||
"vscode-material-icons",
|
||||
"generated",
|
||||
"icons",
|
||||
])
|
||||
|
||||
const codiconsUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "codicons", "codicon.css"])
|
||||
const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "vscode-material-icons"])
|
||||
const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"])
|
||||
const audioUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "audio"])
|
||||
|
||||
|
|
@ -660,40 +647,13 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
|
|||
"assets",
|
||||
"index.css",
|
||||
])
|
||||
// The JS file from the React build output
|
||||
|
||||
const scriptUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "build", "assets", "index.js"])
|
||||
|
||||
// The codicon font from the React build output
|
||||
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-codicons-sample/src/extension.ts
|
||||
// we installed this package in the extension so that we can access it how its intended from the extension (the font file is likely bundled in vscode), and we just import the css fileinto our react app we don't have access to it
|
||||
// don't forget to add font-src ${webview.cspSource};
|
||||
const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [
|
||||
"node_modules",
|
||||
"@vscode",
|
||||
"codicons",
|
||||
"dist",
|
||||
"codicon.css",
|
||||
])
|
||||
|
||||
// The material icons from the React build output
|
||||
const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, [
|
||||
"node_modules",
|
||||
"vscode-material-icons",
|
||||
"generated",
|
||||
"icons",
|
||||
])
|
||||
|
||||
const codiconsUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "codicons", "codicon.css"])
|
||||
const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "vscode-material-icons"])
|
||||
const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"])
|
||||
const audioUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "audio"])
|
||||
|
||||
// const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.js"))
|
||||
|
||||
// const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "reset.css"))
|
||||
// const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "vscode.css"))
|
||||
|
||||
// // Same for stylesheet
|
||||
// const stylesheetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.css"))
|
||||
|
||||
// Use a nonce to only allow a specific script to be run.
|
||||
/*
|
||||
content security policy of your webview to only allow scripts that have a specific nonce
|
||||
|
|
|
|||
121
src/esbuild.mjs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import * as esbuild from "esbuild"
|
||||
import * as path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
import { copyPaths, copyWasms, copyLocales, setupLocaleWatcher } from "@roo-code/build"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
async function main() {
|
||||
const production = process.argv.includes("--production")
|
||||
const watch = process.argv.includes("--watch")
|
||||
const minify = production
|
||||
const sourcemap = !production
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').BuildOptions}
|
||||
*/
|
||||
const buildOptions = {
|
||||
bundle: true,
|
||||
minify,
|
||||
sourcemap,
|
||||
logLevel: "silent",
|
||||
format: "cjs",
|
||||
sourcesContent: false,
|
||||
platform: "node",
|
||||
}
|
||||
|
||||
const srcDir = __dirname
|
||||
const buildDir = __dirname
|
||||
const distDir = path.join(buildDir, "dist")
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').Plugin[]}
|
||||
*/
|
||||
const plugins = [
|
||||
{
|
||||
name: "copy-files",
|
||||
setup(build) {
|
||||
build.onEnd(() => {
|
||||
copyPaths(
|
||||
[
|
||||
["../README.md", "README.md"],
|
||||
["../CHANGELOG.md", "CHANGELOG.md"],
|
||||
["../LICENSE", "LICENSE"],
|
||||
["node_modules/vscode-material-icons/generated", "assets/vscode-material-icons"],
|
||||
["../webview-ui/audio", "webview-ui/audio"],
|
||||
],
|
||||
srcDir,
|
||||
buildDir,
|
||||
)
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "copy-wasms",
|
||||
setup(build) {
|
||||
build.onEnd(() => copyWasms(srcDir, distDir))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "copy-locales",
|
||||
setup(build) {
|
||||
build.onEnd(() => copyLocales(srcDir, distDir))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "esbuild-problem-matcher",
|
||||
setup(build) {
|
||||
build.onStart(() => console.log("[esbuild-problem-matcher#onStart]"))
|
||||
build.onEnd((result) => {
|
||||
result.errors.forEach(({ text, location }) => {
|
||||
console.error(`✘ [ERROR] ${text}`)
|
||||
console.error(` ${location.file}:${location.line}:${location.column}:`)
|
||||
})
|
||||
|
||||
console.log("[esbuild-problem-matcher#onEnd]")
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').BuildOptions}
|
||||
*/
|
||||
const extensionConfig = {
|
||||
...buildOptions,
|
||||
plugins,
|
||||
entryPoints: ["extension.ts"],
|
||||
outfile: "dist/extension.js",
|
||||
external: ["vscode"],
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').BuildOptions}
|
||||
*/
|
||||
const workerConfig = {
|
||||
...buildOptions,
|
||||
entryPoints: ["workers/countTokens.ts"],
|
||||
outdir: "dist/workers",
|
||||
}
|
||||
|
||||
const [extensionCtx, workerCtx] = await Promise.all([
|
||||
esbuild.context(extensionConfig),
|
||||
esbuild.context(workerConfig),
|
||||
])
|
||||
|
||||
if (watch) {
|
||||
await Promise.all([extensionCtx.watch(), workerCtx.watch()])
|
||||
copyLocales(srcDir, distDir)
|
||||
setupLocaleWatcher(srcDir, distDir)
|
||||
} else {
|
||||
await Promise.all([extensionCtx.rebuild(), workerCtx.rebuild()])
|
||||
await Promise.all([extensionCtx.dispose(), workerCtx.dispose()])
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
1
src/exports/roo-code.d.ts
vendored
|
|
@ -1548,6 +1548,7 @@ declare const Package: {
|
|||
readonly name: string
|
||||
readonly version: string
|
||||
readonly outputChannel: string
|
||||
readonly sha: string | undefined
|
||||
}
|
||||
/**
|
||||
* ProviderName
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// This file is automatically generated by running `npm run generate-types`
|
||||
// This file is automatically generated by running `pnpm --filter roo-cline generate-types`
|
||||
// Do not edit it directly.
|
||||
|
||||
type GlobalSettings = {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
extensionContext = context
|
||||
outputChannel = vscode.window.createOutputChannel(Package.outputChannel)
|
||||
context.subscriptions.push(outputChannel)
|
||||
outputChannel.appendLine(`${Package.name} extension activated`)
|
||||
outputChannel.appendLine(`${Package.name} extension activated - ${JSON.stringify(Package)}`)
|
||||
|
||||
// Migrate old settings to new
|
||||
await migrateSettings(context, outputChannel)
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ async function testTerminalCommand(
|
|||
processId: Promise.resolve(123),
|
||||
creationOptions: {},
|
||||
exitStatus: undefined,
|
||||
state: { isInteractedWith: true },
|
||||
state: { isInteractedWith: true, shell: undefined },
|
||||
dispose: jest.fn(),
|
||||
hide: jest.fn(),
|
||||
show: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ async function testCmdCommand(
|
|||
processId: Promise.resolve(123),
|
||||
creationOptions: {},
|
||||
exitStatus: undefined,
|
||||
state: { isInteractedWith: true },
|
||||
state: { isInteractedWith: true, shell: undefined },
|
||||
dispose: jest.fn(),
|
||||
hide: jest.fn(),
|
||||
show: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ async function testPowerShellCommand(
|
|||
processId: Promise.resolve(123),
|
||||
creationOptions: {},
|
||||
exitStatus: undefined,
|
||||
state: { isInteractedWith: true },
|
||||
state: { isInteractedWith: true, shell: undefined },
|
||||
dispose: jest.fn(),
|
||||
hide: jest.fn(),
|
||||
show: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export async function getTheme() {
|
|||
if (currentTheme === undefined && defaultThemes[colorTheme]) {
|
||||
const filename = `${defaultThemes[colorTheme]}.json`
|
||||
currentTheme = await fs.readFile(
|
||||
path.join(getExtensionUri().fsPath, "src", "integrations", "theme", "default-themes", filename),
|
||||
path.join(getExtensionUri().fsPath, "integrations", "theme", "default-themes", filename),
|
||||
"utf-8",
|
||||
)
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ export async function getTheme() {
|
|||
|
||||
if (parsed.include) {
|
||||
const includeThemeString = await fs.readFile(
|
||||
path.join(getExtensionUri().fsPath, "src", "integrations", "theme", "default-themes", parsed.include),
|
||||
path.join(getExtensionUri().fsPath, "integrations", "theme", "default-themes", parsed.include),
|
||||
"utf-8",
|
||||
)
|
||||
const includeTheme = parseThemeString(includeThemeString)
|
||||
|
|
|
|||
|
|
@ -25,25 +25,23 @@ module.exports = {
|
|||
// PowerShell tests are conditionally skipped in the test files themselves using the setupFilesAfterEnv
|
||||
],
|
||||
moduleNameMapper: {
|
||||
"^vscode$": "<rootDir>/src/__mocks__/vscode.js",
|
||||
"@modelcontextprotocol/sdk$": "<rootDir>/src/__mocks__/@modelcontextprotocol/sdk/index.js",
|
||||
"@modelcontextprotocol/sdk/(.*)": "<rootDir>/src/__mocks__/@modelcontextprotocol/sdk/$1",
|
||||
"^delay$": "<rootDir>/src/__mocks__/delay.js",
|
||||
"^p-wait-for$": "<rootDir>/src/__mocks__/p-wait-for.js",
|
||||
"^serialize-error$": "<rootDir>/src/__mocks__/serialize-error.js",
|
||||
"^strip-ansi$": "<rootDir>/src/__mocks__/strip-ansi.js",
|
||||
"^default-shell$": "<rootDir>/src/__mocks__/default-shell.js",
|
||||
"^os-name$": "<rootDir>/src/__mocks__/os-name.js",
|
||||
"^strip-bom$": "<rootDir>/src/__mocks__/strip-bom.js",
|
||||
"^@roo/(.*)$": "<rootDir>/src/$1",
|
||||
"^@src/(.*)$": "<rootDir>/webview-ui/src/$1",
|
||||
"^vscode$": "<rootDir>/__mocks__/vscode.js",
|
||||
"@modelcontextprotocol/sdk$": "<rootDir>/__mocks__/@modelcontextprotocol/sdk/index.js",
|
||||
"@modelcontextprotocol/sdk/(.*)": "<rootDir>/__mocks__/@modelcontextprotocol/sdk/$1",
|
||||
"^delay$": "<rootDir>/__mocks__/delay.js",
|
||||
"^p-wait-for$": "<rootDir>/__mocks__/p-wait-for.js",
|
||||
"^serialize-error$": "<rootDir>/__mocks__/serialize-error.js",
|
||||
"^strip-ansi$": "<rootDir>/__mocks__/strip-ansi.js",
|
||||
"^default-shell$": "<rootDir>/__mocks__/default-shell.js",
|
||||
"^os-name$": "<rootDir>/__mocks__/os-name.js",
|
||||
"^strip-bom$": "<rootDir>/__mocks__/strip-bom.js",
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|serialize-error|strip-ansi|default-shell|os-name|strip-bom)/)",
|
||||
],
|
||||
roots: ["<rootDir>/src", "<rootDir>/webview-ui/src"],
|
||||
modulePathIgnorePatterns: [".vscode-test"],
|
||||
roots: ["<rootDir>"],
|
||||
modulePathIgnorePatterns: ["dist", "out"],
|
||||
reporters: [["jest-simple-dot-reporter", {}]],
|
||||
setupFiles: ["<rootDir>/src/__mocks__/jest.setup.ts"],
|
||||
setupFilesAfterEnv: ["<rootDir>/src/integrations/terminal/__tests__/setupTerminalTests.ts"],
|
||||
setupFiles: ["<rootDir>/__mocks__/jest.setup.ts"],
|
||||
setupFilesAfterEnv: ["<rootDir>/integrations/terminal/__tests__/setupTerminalTests.ts"],
|
||||
}
|
||||
442
src/package.json
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
{
|
||||
"name": "roo-cline",
|
||||
"displayName": "%extension.displayName%",
|
||||
"description": "%extension.description%",
|
||||
"publisher": "RooVeterinaryInc",
|
||||
"version": "3.18.0",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"galleryBanner": {
|
||||
"color": "#617A91",
|
||||
"theme": "dark"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.84.0",
|
||||
"node": "20.18.1"
|
||||
},
|
||||
"author": {
|
||||
"name": "Roo Code"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/RooCodeInc/Roo-Code"
|
||||
},
|
||||
"homepage": "https://github.com/RooCodeInc/Roo-Code",
|
||||
"categories": [
|
||||
"AI",
|
||||
"Chat",
|
||||
"Programming Languages",
|
||||
"Education",
|
||||
"Snippets",
|
||||
"Testing"
|
||||
],
|
||||
"keywords": [
|
||||
"cline",
|
||||
"claude",
|
||||
"dev",
|
||||
"mcp",
|
||||
"openrouter",
|
||||
"coding",
|
||||
"agent",
|
||||
"autonomous",
|
||||
"chatgpt",
|
||||
"sonnet",
|
||||
"ai",
|
||||
"llama",
|
||||
"roo code",
|
||||
"roocode"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage",
|
||||
"onStartupFinished"
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "roo-cline-ActivityBar",
|
||||
"title": "%views.activitybar.title%",
|
||||
"icon": "assets/icons/icon.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"roo-cline-ActivityBar": [
|
||||
{
|
||||
"type": "webview",
|
||||
"id": "roo-cline.SidebarProvider",
|
||||
"name": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "roo-cline.plusButtonClicked",
|
||||
"title": "%command.newTask.title%",
|
||||
"icon": "$(add)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.mcpButtonClicked",
|
||||
"title": "%command.mcpServers.title%",
|
||||
"icon": "$(server)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.promptsButtonClicked",
|
||||
"title": "%command.prompts.title%",
|
||||
"icon": "$(notebook)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.historyButtonClicked",
|
||||
"title": "%command.history.title%",
|
||||
"icon": "$(history)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.popoutButtonClicked",
|
||||
"title": "%command.openInEditor.title%",
|
||||
"icon": "$(link-external)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.settingsButtonClicked",
|
||||
"title": "%command.settings.title%",
|
||||
"icon": "$(settings-gear)"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.openInNewTab",
|
||||
"title": "%command.openInNewTab.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.explainCode",
|
||||
"title": "%command.explainCode.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.fixCode",
|
||||
"title": "%command.fixCode.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.improveCode",
|
||||
"title": "%command.improveCode.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.addToContext",
|
||||
"title": "%command.addToContext.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.newTask",
|
||||
"title": "%command.newTask.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalAddToContext",
|
||||
"title": "%command.terminal.addToContext.title%",
|
||||
"category": "Terminal"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalFixCommand",
|
||||
"title": "%command.terminal.fixCommand.title%",
|
||||
"category": "Terminal"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalExplainCommand",
|
||||
"title": "%command.terminal.explainCommand.title%",
|
||||
"category": "Terminal"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.setCustomStoragePath",
|
||||
"title": "%command.setCustomStoragePath.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.focusInput",
|
||||
"title": "%command.focusInput.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.acceptInput",
|
||||
"title": "%command.acceptInput.title%",
|
||||
"category": "%configuration.title%"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"editor/context": [
|
||||
{
|
||||
"submenu": "roo-cline.contextMenu",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"roo-cline.contextMenu": [
|
||||
{
|
||||
"command": "roo-cline.addToContext",
|
||||
"group": "1_actions@1"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.explainCode",
|
||||
"group": "1_actions@2"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.improveCode",
|
||||
"group": "1_actions@3"
|
||||
}
|
||||
],
|
||||
"terminal/context": [
|
||||
{
|
||||
"submenu": "roo-cline.terminalMenu",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"roo-cline.terminalMenu": [
|
||||
{
|
||||
"command": "roo-cline.terminalAddToContext",
|
||||
"group": "1_actions@1"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalFixCommand",
|
||||
"group": "1_actions@2"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.terminalExplainCommand",
|
||||
"group": "1_actions@3"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "roo-cline.plusButtonClicked",
|
||||
"group": "navigation@1",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.promptsButtonClicked",
|
||||
"group": "navigation@2",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.mcpButtonClicked",
|
||||
"group": "navigation@3",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.historyButtonClicked",
|
||||
"group": "navigation@4",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.popoutButtonClicked",
|
||||
"group": "navigation@5",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.settingsButtonClicked",
|
||||
"group": "navigation@6",
|
||||
"when": "view == roo-cline.SidebarProvider"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "roo-cline.plusButtonClicked",
|
||||
"group": "navigation@1",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.promptsButtonClicked",
|
||||
"group": "navigation@2",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.mcpButtonClicked",
|
||||
"group": "navigation@3",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.historyButtonClicked",
|
||||
"group": "navigation@4",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.popoutButtonClicked",
|
||||
"group": "navigation@5",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.settingsButtonClicked",
|
||||
"group": "navigation@6",
|
||||
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
|
||||
}
|
||||
]
|
||||
},
|
||||
"submenus": [
|
||||
{
|
||||
"id": "roo-cline.contextMenu",
|
||||
"label": "%views.contextMenu.label%"
|
||||
},
|
||||
{
|
||||
"id": "roo-cline.terminalMenu",
|
||||
"label": "%views.terminalMenu.label%"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "%configuration.title%",
|
||||
"properties": {
|
||||
"roo-cline.allowedCommands": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"npm test",
|
||||
"npm install",
|
||||
"tsc",
|
||||
"git log",
|
||||
"git diff",
|
||||
"git show"
|
||||
],
|
||||
"description": "%commands.allowedCommands.description%"
|
||||
},
|
||||
"roo-cline.vsCodeLmModelSelector": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"description": "%settings.vsCodeLmModelSelector.vendor.description%"
|
||||
},
|
||||
"family": {
|
||||
"type": "string",
|
||||
"description": "%settings.vsCodeLmModelSelector.family.description%"
|
||||
}
|
||||
},
|
||||
"description": "%settings.vsCodeLmModelSelector.description%"
|
||||
},
|
||||
"roo-cline.customStoragePath": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "%settings.customStoragePath.description%"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint **/*.ts",
|
||||
"check-types": "tsc --noEmit",
|
||||
"pretest": "pnpm bundle",
|
||||
"test": "jest -w=40% && vitest run",
|
||||
"format": "prettier --write .",
|
||||
"bundle": "pnpm clean && pnpm --filter @roo-code/build build && node esbuild.mjs",
|
||||
"build": "pnpm bundle --production && pnpm --filter @roo-code/vscode-webview build",
|
||||
"build:development": "pnpm bundle && pnpm --filter @roo-code/vscode-webview build",
|
||||
"publish:marketplace": "vsce publish --no-dependencies && ovsx publish --no-dependencies",
|
||||
"publish": "pnpm vsix && changeset publish && pnpm install --package-lock-only",
|
||||
"version-packages": "changeset version && pnpm install --package-lock-only",
|
||||
"vscode:prepublish": "pnpm build",
|
||||
"vsix": "mkdirp ../bin && npx vsce package --no-dependencies --out ../bin",
|
||||
"watch:esbuild": "pnpm bundle --watch",
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"generate-types": "tsx scripts/generate-types.mts",
|
||||
"clean": "rimraf README.md CHANGELOG.md LICENSE dist webview-ui out .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/bedrock-sdk": "^0.10.2",
|
||||
"@anthropic-ai/sdk": "^0.37.0",
|
||||
"@anthropic-ai/vertex-sdk": "^0.7.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.779.0",
|
||||
"@aws-sdk/credential-providers": "^3.806.0",
|
||||
"@google/genai": "^0.13.0",
|
||||
"@mistralai/mistralai": "^1.3.6",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"axios": "^1.7.4",
|
||||
"cheerio": "^1.0.0",
|
||||
"chokidar": "^4.0.1",
|
||||
"clone-deep": "^4.0.1",
|
||||
"default-shell": "^2.2.0",
|
||||
"delay": "^6.0.0",
|
||||
"diff": "^5.2.0",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-xml-parser": "^4.5.1",
|
||||
"fastest-levenshtein": "^1.0.16",
|
||||
"fzf": "^0.5.2",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"google-auth-library": "^9.15.1",
|
||||
"i18next": "^24.2.2",
|
||||
"ignore": "^7.0.3",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"mammoth": "^1.8.0",
|
||||
"monaco-vscode-textmate-theme-converter": "^0.1.7",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-ipc": "^12.0.0",
|
||||
"openai": "^4.78.1",
|
||||
"os-name": "^6.0.0",
|
||||
"p-wait-for": "^5.0.2",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"pkce-challenge": "^4.1.0",
|
||||
"posthog-node": "^4.7.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"ps-tree": "^1.2.0",
|
||||
"puppeteer-chromium-resolver": "^23.0.0",
|
||||
"puppeteer-core": "^23.4.0",
|
||||
"reconnecting-eventsource": "^1.6.4",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"say": "^0.16.0",
|
||||
"serialize-error": "^11.0.3",
|
||||
"simple-git": "^3.27.0",
|
||||
"sound-play": "^1.1.0",
|
||||
"string-similarity": "^4.0.4",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"strip-bom": "^5.0.0",
|
||||
"tiktoken": "^1.0.21",
|
||||
"tmp": "^0.2.3",
|
||||
"tree-sitter-wasms": "^0.1.11",
|
||||
"turndown": "^7.2.0",
|
||||
"vscode-material-icons": "^0.1.1",
|
||||
"web-tree-sitter": "^0.22.6",
|
||||
"workerpool": "^9.2.0",
|
||||
"yaml": "^2.8.0",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.27.10",
|
||||
"@changesets/types": "^6.0.0",
|
||||
"@jest/globals": "^29.7.0",
|
||||
"@roo-code/build": "workspace:^",
|
||||
"@types/clone-deep": "^4.0.4",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/diff": "^5.2.1",
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "20.x",
|
||||
"@types/node-cache": "^4.1.3",
|
||||
"@types/node-ipc": "^9.2.3",
|
||||
"@types/ps-tree": "^1.1.6",
|
||||
"@types/string-similarity": "^4.0.2",
|
||||
"@types/tmp": "^0.2.6",
|
||||
"@types/turndown": "^5.0.5",
|
||||
"@types/vscode": "^1.84.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"@vscode/vsce": "3.3.2",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^8.57.0",
|
||||
"execa": "^9.5.2",
|
||||
"glob": "^11.0.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-simple-dot-reporter": "^1.0.5",
|
||||
"mkdirp": "^3.0.1",
|
||||
"nock": "^14.0.4",
|
||||
"npm-run-all2": "^8.0.1",
|
||||
"ovsx": "0.10.2",
|
||||
"prettier": "^3.4.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-jest": "^29.2.5",
|
||||
"tsup": "^8.4.0",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "5.8.3",
|
||||
"vitest": "^3.1.3",
|
||||
"zod-to-ts": "^1.2.0"
|
||||
}
|
||||
}
|
||||