diff --git a/.changeset/config.json b/.changeset/config.json index ab848d1a1e..c8fca743d8 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,5 +1,5 @@ { - "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", + "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index cad57bdc0e..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,11 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Feature Request - url: https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop - about: Share and vote on feature requests for Cline - - name: Cline Discord - url: https://discord.gg/cline - about: Join our Discord community for discussions and support - - name: Other Questions? - url: https://x.com/sdrzn - about: Contact the developer on X @sdrzn for other inquiries diff --git a/.github/actions/ai-release-notes/action.yml b/.github/actions/ai-release-notes/action.yml new file mode 100644 index 0000000000..294e64500c --- /dev/null +++ b/.github/actions/ai-release-notes/action.yml @@ -0,0 +1,86 @@ +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: false + type: string + default: '' + head_ref: + required: false + type: string + default: main + base_ref: + required: false + type: string + default: main + +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 == '' && github.event.pull_request.head.ref || inputs.git_ref }} + BASE_REF: ${{ inputs.base_ref == '' && github.base_ref || inputs.base_ref }} + HEAD_REF: ${{ inputs.head_ref == '' && github.event.pull_request.head.sha || 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 + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..5f0889ce91 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "npm" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..a689eb9ab3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,28 @@ + +## Description + +## Type of change + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## How Has This Been Tested? + + +## Checklist: + +- [ ] My code follows the patterns of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation + +## Additional context + + +## Related Issues + + +## Reviewers + \ No newline at end of file diff --git a/.github/scripts/ai-release-notes.py b/.github/scripts/ai-release-notes.py new file mode 100644 index 0000000000..5dc451810e --- /dev/null +++ b/.github/scripts/ai-release-notes.py @@ -0,0 +1,123 @@ +""" +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<= 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() \ No newline at end of file diff --git a/.github/scripts/overwrite_changeset_changelog.py b/.github/scripts/overwrite_changeset_changelog.py new file mode 100755 index 0000000000..d9ce3001ec --- /dev/null +++ b/.github/scripts/overwrite_changeset_changelog.py @@ -0,0 +1,60 @@ +""" +This script updates a specific version's release notes section in CHANGELOG.md with new content. + +The script: +1. Takes a version number, changelog path, and new content as input from environment variables +2. Finds the section in the changelog for the specified version +3. Replaces the content between the current version header and the next version header + (or end of file if it's the latest version) with the new content +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 notes for + PREV_VERSION: The previous version number (optional) + NEW_CONTENT: The 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['NEW_CONTENT'] + +def overwrite_changelog_section(content: str): + """Replace a specific version section in the changelog content. + + Args: + content: The full changelog content as a string + + Returns: + The updated changelog content with the new section + + Example: + >>> content = "## 1.2.0\\nOld changes\\n## 1.1.0\\nOld changes" + >>> NEW_CONTENT = "New changes" + >>> overwrite_changelog_section(content) + '## 1.2.0\\nNew changes\\n## 1.1.0\\nOld changes' + """ + # 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) + 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 and prev_version_pattern in content else len(content) + return content[:notes_start_index] + f"{NEW_CONTENT}\n" + content[notes_end_index:] + +with open(CHANGELOG_PATH, 'r') as f: + content = f.read() + +new_changelog = overwrite_changelog_section(content) + +print(new_changelog) + +# Write back to CHANGELOG.md +with open(CHANGELOG_PATH, 'w') as f: + f.write(new_changelog) diff --git a/.github/scripts/parse_changeset_changelog.py b/.github/scripts/parse_changeset_changelog.py new file mode 100755 index 0000000000..b21c444dd1 --- /dev/null +++ b/.github/scripts/parse_changeset_changelog.py @@ -0,0 +1,64 @@ +""" +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< and that contains [!IMPORTANT] + ellipsis_match = re.search(r'(.*?)', 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: . +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< + github.event_name == 'pull_request' && + github.event.pull_request.merged == true && + github.event.pull_request.base.ref == 'main' && + github.actor != 'R00-B0T' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Git Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.GIT_REF }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install Dependencies + run: npm install + + # Check if there are any new changesets to process + - name: Check for changesets + id: check-changesets + run: | + NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ') + echo "Changesets diff with previous version: $NEW_CHANGESETS" + echo "new_changesets=$NEW_CHANGESETS" >> $GITHUB_OUTPUT + + # Create version bump PR using changesets/action if there are new changesets + - name: Changeset Pull Request + if: steps.check-changesets.outputs.new_changesets != '0' + id: changesets + uses: changesets/action@v1 + with: + commit: "changeset version bump" + title: "Changeset version bump" + version: npm run version-packages # This performs the changeset version bump + env: + GITHUB_TOKEN: ${{ secrets.CROSS_REPO_ACCESS_TOKEN }} + + # Job 2: Process version bump PR created by R00-B0T + changeset-pr-approve-merge: + name: Auto approve and merge Bump version PRs + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + if: > + github.event_name == 'pull_request' && + github.event.pull_request.base.ref == 'main' && + github.actor == 'R00-B0T' && + contains(github.event.pull_request.title, 'Changeset version bump') + + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + token: ${{ secrets.CROSS_REPO_ACCESS_TOKEN }} + fetch-depth: 0 + ref: ${{ env.GIT_REF }} + + # Get current and previous versions for changelog processing + - name: Get version + id: get_version + run: | + VERSION=$(git show HEAD:package.json | jq -r '.version') + echo "version=$VERSION" >> $GITHUB_OUTPUT + PREV_VERSION=$(git show origin/main:package.json | jq -r '.version') + echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT + + echo "version=$VERSION" + echo "prev_version=$PREV_VERSION" + + # Get previous version refs, GITHUB_OUTPUT: 'BASE_REF' and 'HEAD_REF' + - name: Get Previous Version Refs + id: version_refs + run: python .github/scripts/get_prev_version_refs.py + + # Generate release notes using OpenAI if not already edited, GITHUB_OUTPUT: 'RELEASE_NOTES' and 'OPENAI_PROMPT' + - name: AI Release Notes + if: ${{ !contains(github.event.pull_request.labels.*.name, 'openai-edited') }} + uses: ./.github/actions/ai-release-notes + id: ai_release_notes + with: + GHA_PAT: ${{ secrets.CROSS_REPO_ACCESS_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + model_name: gpt-4o-mini + repo_path: ${{ env.REPO_PATH }} + base_ref: ${{ steps.version_refs.outputs.base_ref }} + head_ref: ${{ steps.version_refs.outputs.head_ref }} + + # Update CHANGELOG.md with AI-generated notes + - name: Update Changeset Changelog + if: ${{ !contains(github.event.pull_request.labels.*.name, 'openai-edited') }} + env: + VERSION: ${{ steps.get_version.outputs.version }} + PREV_VERSION: ${{ steps.get_version.outputs.prev_version }} + NEW_CONTENT: ${{ steps.ai_release_notes.outputs.RELEASE_NOTES }} + run: python .github/scripts/overwrite_changeset_changelog.py + + # Commit and push changelog updates + - name: Push Changelog updates + if: ${{ !contains(github.event.pull_request.labels.*.name, 'openai-edited') }} + run: | + git config user.name "R00-B0T" + git config user.email github-actions@github.com + git status + + echo "Updating changelog.md..." + git add CHANGELOG.md + git commit -m "Updating changeset changelog" + + echo "--------------------------------------------------------------------------------" + echo "Pushing to remote..." + echo "--------------------------------------------------------------------------------" + git push + + # Add label to indicate OpenAI has processed this PR + - name: Add openai-edited label + if: ${{ !contains(github.event.pull_request.labels.*.name, 'openai-edited') }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ['openai-edited'] + }); + + # Auto-approve PR once OpenAI has processed it + - name: Auto approve PR + if: contains(github.event.pull_request.labels.*.name, 'openai-edited') + uses: hmarr/auto-approve-action@v4 + with: + review-message: "I'm approving since it's a bump version PR" + + # Enable auto-merge for the PR + - name: Enable automerge on PR + if: contains(github.event.pull_request.labels.*.name, 'openai-edited') + run: gh pr merge --squash --auto ${{ github.event.pull_request.number }} + env: + GH_TOKEN: ${{ secrets.CROSS_REPO_ACCESS_TOKEN }} + + # Job 3: Create GitHub release after version bump PR is merged + github-release: + runs-on: ubuntu-latest + if: > + github.event_name == 'pull_request' && + github.event.pull_request.merged == true && + github.event.pull_request.base.ref == 'main' && + github.actor == 'R00-B0T' && + contains(github.event.pull_request.title, 'Changeset version bump') + permissions: + contents: write + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + + - name: Get version + id: get_version + run: | + VERSION=$(git show HEAD:package.json | jq -r '.version') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Extract release notes from CHANGELOG.md, GITHUB_OUTPUT: 'release-notes' + - name: Parse CHANGELOG.md + id: changelog + env: + CHANGELOG_PATH: CHANGELOG.md + VERSION: ${{ steps.get_version.outputs.version }} + run: python .github/scripts/parse_changeset_changelog.py + + # Create GitHub release with extracted notes + - name: Create or Update Release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.get_version.outputs.version }} + name: Release v${{ steps.get_version.outputs.version }} + draft: false + prerelease: false + append_body: false + make_latest: true + body: ${{ steps.changelog.outputs.release-notes }} diff --git a/.github/workflows/marketplace-publish.yml b/.github/workflows/marketplace-publish.yml index 11756fb259..07d6144d27 100644 --- a/.github/workflows/marketplace-publish.yml +++ b/.github/workflows/marketplace-publish.yml @@ -8,6 +8,7 @@ jobs: publish-extension: if: ${{ github.actor == 'R00-B0T'}} runs-on: ubuntu-latest + if: ${{ github.actor == 'R00-B0T'}} steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/.gitignore b/.gitignore index cc3df8e1c8..91e0bb9331 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,6 @@ node_modules bin roo-cline-*.vsix -# Prompts +# Local prompts prompts .clinerules diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000000..2f3977a2da --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,9 @@ +branch="$(git rev-parse --abbrev-ref HEAD)" + +if [ "$branch" = "main" ]; then + echo "You can't commit directly to main - please check out a branch." + exit 1 +fi + +npx lint-staged +npm run compile diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 0000000000..ab7d11b507 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,17 @@ +branch="$(git rev-parse --abbrev-ref HEAD)" + +if [ "$branch" = "main" ]; then + echo "You can't push directly to main - please check out a branch." + exit 1 +fi + +npm run compile + +# Check for new changesets +NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ') +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 "-------------------------------------------------------------------------------------" +fi \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 05177c310d..0920e85695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,72 +1,86 @@ # Roo Cline Changelog +## 2.1.21 + +### Patch Changes + +- 8dbd019: Larger Promp Text Input + +## [2.1.20] + +- Add Gemini 2.0 + +## [2.1.19] + +- Better error handling for diff editing + ## [2.1.18] -- Diff editing bugfix to handle Windows line endings +- Diff editing bugfix to handle Windows line endings ## [2.1.17] -- Switch to search/replace diffs in experimental diff editing mode +- Switch to search/replace diffs in experimental diff editing mode ## [2.1.16] -- Allow copying prompts from the history screen +- Allow copying prompts from the history screen ## [2.1.15] -- Incorporate dbasclpy's [PR](https://github.com/RooVetGit/Roo-Cline/pull/54) to add support for gemini-exp-1206 -- Make it clear that diff editing is very experimental +- Incorporate dbasclpy's [PR](https://github.com/RooVetGit/Roo-Cline/pull/54) to add support for gemini-exp-1206 +- Make it clear that diff editing is very experimental ## [2.1.14] -- Fix bug where diffs were not being applied correctly and try Aider's [unified diff prompt](https://github.com/Aider-AI/aider/blob/3995accd0ca71cea90ef76d516837f8c2731b9fe/aider/coders/udiff_prompts.py#L75-L105) -- If diffs are enabled, automatically reject write_to_file commands that lead to truncated output +- Fix bug where diffs were not being applied correctly and try Aider's [unified diff prompt](https://github.com/Aider-AI/aider/blob/3995accd0ca71cea90ef76d516837f8c2731b9fe/aider/coders/udiff_prompts.py#L75-L105) +- If diffs are enabled, automatically reject write_to_file commands that lead to truncated output ## [2.1.13] -- Fix https://github.com/RooVetGit/Roo-Cline/issues/50 where sound effects were not respecting settings +- Fix https://github.com/RooVetGit/Roo-Cline/issues/50 where sound effects were not respecting settings ## [2.1.12] -- Incorporate JoziGila's [PR](https://github.com/cline/cline/pull/158) to add support for editing through diffs +- Incorporate JoziGila's [PR](https://github.com/cline/cline/pull/158) to add support for editing through diffs ## [2.1.11] -- Incorporate lloydchang's [PR](https://github.com/RooVetGit/Roo-Cline/pull/42) to add support for OpenRouter compression +- Incorporate lloydchang's [PR](https://github.com/RooVetGit/Roo-Cline/pull/42) to add support for OpenRouter compression ## [2.1.10] -- Incorporate HeavenOSK's [PR](https://github.com/cline/cline/pull/818) to add sound effects to Cline +- Incorporate HeavenOSK's [PR](https://github.com/cline/cline/pull/818) to add sound effects to Cline ## [2.1.9] -- Add instructions for using .clinerules on the settings screen +- Add instructions for using .clinerules on the settings screen ## [2.1.8] -- Roo Cline now allows configuration of which commands are allowed without approval! +- Roo Cline now allows configuration of which commands are allowed without approval! ## [2.1.7] -- Updated extension icon and metadata +- Updated extension icon and metadata ## [2.1.6] -- Roo Cline now runs in all VSCode-compatible editors +- Roo Cline now runs in all VSCode-compatible editors ## [2.1.5] -- Fix bug in browser action approval +- Fix bug in browser action approval ## [2.1.4] -- Roo Cline now can run side-by-side with Cline +- Roo Cline now can run side-by-side with Cline ## [2.1.3] -- Roo Cline now allows browser actions without approval when `alwaysAllowBrowser` is true +- Roo Cline now allows browser actions without approval when `alwaysAllowBrowser` is true ## [2.1.2] -- Support for auto-approval of write operations and command execution -- Support for .clinerules custom instructions +- Support for auto-approval of write operations and command execution +- Support for .clinerules custom instructions diff --git a/README.md b/README.md index 57c80a599c..d5c92aa029 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,12 @@ Here's an example of Roo-Cline autonomously creating a snake game with "Always a https://github.com/user-attachments/assets/c2bb31dc-e9b2-4d73-885d-17f1471a4987 -### Installation -If you want to install the latest extension from the Marketplace, just search for "Roo Cline" in your VSCode-compatible editor's Extensions panel (Cmd/Ctrl+Shift+X). +## Contributing +To contribute to the project, start by exploring [open issues](https://github.com/RooVetGit/Roo-Cline/issues) or checking our [feature request board](https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop). We'd also love to have you join our [Discord](https://discord.gg/cline) to share ideas and connect with other contributors. +
+Local Setup -### Testing Builds 1. Install dependencies: ```bash npm run install:all @@ -40,7 +41,12 @@ If you want to install the latest extension from the Marketplace, just search fo # Ex: code --install-extension bin/roo-cline-2.0.1.vsix ``` -### Publishing +5. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.) + +
+ +
+Publishing We use [changesets](https://github.com/changesets/changesets) for versioning and publishing this package. To make changes: 1. Create a PR with your changes @@ -54,34 +60,15 @@ Once your merge is successful: - The release workflow will automatically create a new "Changeset version bump" PR - This PR will: - Update the version based on your changeset - - Update the CHANGELOG.md file + - Update the `CHANGELOG.md` file - Create a git tag - The PR will be automatically approved and merged -- A new version will be published +- A new version and git release will be published -### Browser Actions +
-The browser action tool allows Cline to interact with web pages through a Puppeteer-controlled browser. This can be used in two modes: - -#### Interactive Mode -- Triggered by adding the string `interactive mode` to your prompt, it also requires the string `(browserPort = 7333)`, where the port number can be modified. -- Requires a browser instance running on the specified port, ex: `/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --remote-debugging-port=7333` -- Useful for debugging and development scenarios where you need to interact with a running browser - -#### Non-Interactive Mode -- Default mode when no `interactive mode` string is present in your prompt -- Launches a new browser instance for each session -- Better suited for quick simple browser tasks, ex: testing a URL - -The browser action, in both modes, supports operations like: -- Launching a browser at a specified URL -- Clicking elements at specific coordinates -- Typing text -- Scrolling the page -- Capturing screenshots and console logs -- Closing the browser - -Each browser action provides feedback through screenshots and console logs, allowing for detailed interaction tracking and debugging. +## Stay Updated! +Subscribe to our [Github releases](https://github.com/RooVetGit/Roo-Cline/releases) to keep up with the latest updates! You can also view our [CHANGELOG.md](Roo-Cline/CHANGELOG.md) for more details. --- @@ -181,30 +168,3 @@ Try asking Cline to "test the app", and watch as he runs a command like `npm run - **`@problems`:** Add workspace errors and warnings ('Problems' panel) for Cline to fix - **`@file`:** Adds a file's contents so you don't have to waste API requests approving read file (+ type to search files) - **`@folder`:** Adds folder's files all at once to speed up your workflow even more - -## Contributing - -To contribute to the project, start by exploring [open issues](https://github.com/cline/cline/issues) or checking our [feature request board](https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop). We'd also love to have you join our [Discord](https://discord.gg/cline) to share ideas and connect with other contributors. If you're interested in joining the team, check out our [careers page](https://cline.bot/join-us)! - -
-Local Development Instructions - -1. Clone the repository _(Requires [git-lfs](https://git-lfs.com/))_: - ```bash - git clone https://github.com/cline/cline.git - ``` -2. Open the project in VSCode: - ```bash - code cline - ``` -3. Install the necessary dependencies for the extension and webview-gui: - ```bash - npm run install:all - ``` -4. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.) - -
- -## License - -[Apache 2.0 © 2024 Cline Bot Inc.](./LICENSE) diff --git a/package-lock.json b/package-lock.json index fb30b0cd87..0118cd48bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "2.1.18", + "version": "2.1.21", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "2.1.18", + "version": "2.1.21", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.26.0", @@ -42,7 +42,7 @@ "web-tree-sitter": "^0.22.6" }, "devDependencies": { - "@changesets/cli": "^2.27.1", + "@changesets/cli": "^2.27.10", "@types/diff": "^5.2.1", "@types/jest": "^29.5.14", "@types/mocha": "^10.0.7", @@ -53,7 +53,9 @@ "@vscode/test-electron": "^2.4.0", "esbuild": "^0.24.0", "eslint": "^8.57.0", + "husky": "^9.1.7", "jest": "^29.7.0", + "lint-staged": "^15.2.11", "npm-run-all": "^4.1.5", "ts-jest": "^29.2.5", "typescript": "^5.4.5" @@ -7332,6 +7334,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -7443,6 +7484,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -8077,6 +8124,18 @@ "node": ">=6" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -8505,6 +8564,12 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -9009,6 +9074,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -9382,6 +9459,21 @@ "ms": "^2.0.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -11022,6 +11114,18 @@ "immediate": "~3.0.5" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -11036,6 +11140,257 @@ "uc.micro": "^1.0.1" } }, + "node_modules/lint-staged": { + "version": "15.2.11", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.11.tgz", + "integrity": "sha512-Ev6ivCTYRTGs9ychvpVw35m/bcNDuBN+mnTeObCL5h+boS5WzBEC6LHI4I9F/++sZm1m+J2LEiy0gxL/R9TBqQ==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.4.0", + "execa": "~8.0.1", + "lilconfig": "~3.1.3", + "listr2": "~8.2.5", + "micromatch": "~4.0.8", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.6.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -11122,6 +11477,169 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/lop": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", @@ -11319,6 +11837,18 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -12283,9 +12813,9 @@ "dev": true }, "node_modules/package-manager-detector": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.5.tgz", - "integrity": "sha512-3dS7y28uua+UDbRCLBqltMBrbI+A5U2mI9YuxHRxIWYmLj3DwntEBmERYzIAQ4DMeuCUOBSak7dBHHoXKpOTYQ==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.7.tgz", + "integrity": "sha512-g4+387DXDKlZzHkP+9FLt8yKj8+/3tOkPv7DVTJGGRm00RkEWgqbFstX1mXJ4M0VDYhUqsTOiISqNOJnhAu3PQ==", "dev": true }, "node_modules/pako": { @@ -13230,6 +13760,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -13593,6 +14129,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -13756,6 +14332,15 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -15243,6 +15828,18 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index f090f2518f..752ae42dcd 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Roo Cline", "description": "A fork of Cline, an autonomous coding agent, with some added experimental configuration and automation features.", "publisher": "RooVeterinaryInc", - "version": "2.1.18", + "version": "2.1.21", "icon": "assets/icons/rocket.png", "galleryBanner": { "color": "#617A91", @@ -152,14 +152,14 @@ "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": "npx vsce package --out bin", + "vsix": "mkdir -p bin && npx vsce package --out bin", "watch": "npm-run-all -p watch:*", "watch:esbuild": "node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", "watch-tests": "tsc -p . -w --outDir out" }, "devDependencies": { - "@changesets/cli": "^2.27.1", + "@changesets/cli": "^2.27.10", "@types/diff": "^5.2.1", "@types/jest": "^29.5.14", "@types/mocha": "^10.0.7", @@ -170,7 +170,9 @@ "@vscode/test-electron": "^2.4.0", "esbuild": "^0.24.0", "eslint": "^8.57.0", + "husky": "^9.1.7", "jest": "^29.7.0", + "lint-staged": "^15.2.11", "npm-run-all": "^4.1.5", "ts-jest": "^29.2.5", "typescript": "^5.4.5" @@ -208,5 +210,10 @@ "turndown": "^7.2.0", "vsce": "^2.15.0", "web-tree-sitter": "^0.22.6" + }, + "lint-staged": { + "src/**/*.{ts,tsx}": [ + "npx eslint -c .eslintrc.json" + ] } } diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 57cab17e68..f1c576bd26 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -17,7 +17,8 @@ export class OpenAiHandler implements ApiHandler { constructor(options: ApiHandlerOptions) { this.options = options // Azure API shape slightly differs from the core API shape: https://github.com/openai/openai-node?tab=readme-ov-file#microsoft-azure-openai - if (this.options.openAiBaseUrl?.toLowerCase().includes("azure.com")) { + const urlHost = new URL(this.options.openAiBaseUrl ?? "").host; + if (urlHost === "azure.com" || urlHost.endsWith(".azure.com")) { this.client = new AzureOpenAI({ baseURL: this.options.openAiBaseUrl, apiKey: this.options.openAiApiKey, diff --git a/src/core/Cline.ts b/src/core/Cline.ts index bf1033ea34..a42996b277 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1234,12 +1234,12 @@ export class Cline { pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "diff")) break } - this.consecutiveMistakeCount = 0 const absolutePath = path.resolve(cwd, relPath) const fileExists = await fileExistsAtPath(absolutePath) if (!fileExists) { + this.consecutiveMistakeCount++ await this.say("error", `File does not exist at path: ${absolutePath}`) pushToolResult(`Error: File does not exist at path: ${absolutePath}`) break @@ -1250,11 +1250,14 @@ export class Cline { // Apply the diff to the original content let newContent = this.diffStrategy?.applyDiff(originalContent, diffContent) ?? false if (newContent === false) { - await this.say("error", `Error applying diff to file: ${absolutePath}`) - pushToolResult(`Error applying diff to file: ${absolutePath}`) + this.consecutiveMistakeCount++ + await this.say("error", `Unable to apply diff to file - contents are out of sync: ${absolutePath}`) + pushToolResult(`Error applying diff to file: ${absolutePath} - contents are out of sync. Try re-reading the relevant lines of the file and applying the diff again.`) break } + this.consecutiveMistakeCount = 0 + // Show diff view before asking for approval this.diffViewProvider.editType = "modify" await this.diffViewProvider.open(relPath); diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index c4dced015c..797bb90f37 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -124,17 +124,18 @@ export class DiffViewProvider { edit.delete(document.uri, new vscode.Range(this.streamedLines.length, 0, document.lineCount, 0)) await vscode.workspace.applyEdit(edit) } - // Add empty last line if original content had one + // Preserve empty last line if original content had one const hasEmptyLastLine = this.originalContent?.endsWith("\n") - if (hasEmptyLastLine) { - const accumulatedLines = accumulatedContent.split("\n") - if (accumulatedLines[accumulatedLines.length - 1] !== "") { - accumulatedContent += "\n" - } + if (hasEmptyLastLine && !accumulatedContent.endsWith("\n")) { + accumulatedContent += "\n" } - // Clear all decorations at the end (before applying final edit) - this.fadedOverlayController.clear() - this.activeLineController.clear() + // Apply the final content + const finalEdit = new vscode.WorkspaceEdit() + finalEdit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), accumulatedContent) + await vscode.workspace.applyEdit(finalEdit) + // Clear all decorations at the end (after applying final edit) + this.fadedOverlayController.clear() + this.activeLineController.clear() } } @@ -351,4 +352,4 @@ export class DiffViewProvider { this.streamedLines = [] this.preDiagnostics = [] } -} +} \ No newline at end of file diff --git a/src/integrations/editor/__tests__/DiffViewProvider.test.ts b/src/integrations/editor/__tests__/DiffViewProvider.test.ts new file mode 100644 index 0000000000..debc408fed --- /dev/null +++ b/src/integrations/editor/__tests__/DiffViewProvider.test.ts @@ -0,0 +1,100 @@ +import { DiffViewProvider } from '../DiffViewProvider'; +import * as vscode from 'vscode'; + +// Mock vscode +jest.mock('vscode', () => ({ + workspace: { + applyEdit: jest.fn(), + }, + window: { + createTextEditorDecorationType: jest.fn(), + }, + WorkspaceEdit: jest.fn().mockImplementation(() => ({ + replace: jest.fn(), + delete: jest.fn(), + })), + Range: jest.fn(), + Position: jest.fn(), + Selection: jest.fn(), + TextEditorRevealType: { + InCenter: 2, + }, +})); + +// Mock DecorationController +jest.mock('../DecorationController', () => ({ + DecorationController: jest.fn().mockImplementation(() => ({ + setActiveLine: jest.fn(), + updateOverlayAfterLine: jest.fn(), + clear: jest.fn(), + })), +})); + +describe('DiffViewProvider', () => { + let diffViewProvider: DiffViewProvider; + const mockCwd = '/mock/cwd'; + let mockWorkspaceEdit: { replace: jest.Mock; delete: jest.Mock }; + + beforeEach(() => { + jest.clearAllMocks(); + mockWorkspaceEdit = { + replace: jest.fn(), + delete: jest.fn(), + }; + (vscode.WorkspaceEdit as jest.Mock).mockImplementation(() => mockWorkspaceEdit); + + diffViewProvider = new DiffViewProvider(mockCwd); + // Mock the necessary properties and methods + (diffViewProvider as any).relPath = 'test.txt'; + (diffViewProvider as any).activeDiffEditor = { + document: { + uri: { fsPath: `${mockCwd}/test.txt` }, + getText: jest.fn(), + lineCount: 10, + }, + selection: { + active: { line: 0, character: 0 }, + anchor: { line: 0, character: 0 }, + }, + edit: jest.fn().mockResolvedValue(true), + revealRange: jest.fn(), + }; + (diffViewProvider as any).activeLineController = { setActiveLine: jest.fn(), clear: jest.fn() }; + (diffViewProvider as any).fadedOverlayController = { updateOverlayAfterLine: jest.fn(), clear: jest.fn() }; + }); + + describe('update method', () => { + it('should preserve empty last line when original content has one', async () => { + (diffViewProvider as any).originalContent = 'Original content\n'; + await diffViewProvider.update('New content', true); + + expect(mockWorkspaceEdit.replace).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + 'New content\n' + ); + }); + + it('should not add extra newline when accumulated content already ends with one', async () => { + (diffViewProvider as any).originalContent = 'Original content\n'; + await diffViewProvider.update('New content\n', true); + + expect(mockWorkspaceEdit.replace).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + 'New content\n' + ); + }); + + it('should not add newline when original content does not end with one', async () => { + (diffViewProvider as any).originalContent = 'Original content'; + await diffViewProvider.update('New content', true); + + expect(mockWorkspaceEdit.replace).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + 'New content' + ); + }); + }); +}); \ No newline at end of file diff --git a/src/shared/api.ts b/src/shared/api.ts index 0ea4622d99..94d0b1843a 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -286,6 +286,14 @@ export const geminiModels = { inputPrice: 0, outputPrice: 0, }, + "gemini-2.0-flash-exp": { + maxTokens: 8192, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + }, } as const satisfies Record // OpenAI Native diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index f465942f36..f48380b95f 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -13,6 +13,8 @@ import { MAX_IMAGES_PER_MESSAGE } from "./ChatView" import ContextMenu from "./ContextMenu" import Thumbnails from "../common/Thumbnails" +declare const vscode: any; + interface ChatTextAreaProps { inputValue: string setInputValue: (value: string) => void @@ -427,7 +429,62 @@ const ChatTextArea = forwardRef( opacity: textAreaDisabled ? 0.5 : 1, position: "relative", display: "flex", - }}> + }} + onDrop={async (e) => { + console.log("onDrop called") + e.preventDefault() + const files = Array.from(e.dataTransfer.files) + const text = e.dataTransfer.getData("text") + if (text) { + const newValue = + inputValue.slice(0, cursorPosition) + text + inputValue.slice(cursorPosition) + setInputValue(newValue) + const newCursorPosition = cursorPosition + text.length + setCursorPosition(newCursorPosition) + setIntendedCursorPosition(newCursorPosition) + return + } + const acceptedTypes = ["png", "jpeg", "webp"] + const imageFiles = files.filter((file) => { + const [type, subtype] = file.type.split("/") + return type === "image" && acceptedTypes.includes(subtype) + }) + if (!shouldDisableImages && imageFiles.length > 0) { + const imagePromises = imageFiles.map((file) => { + return new Promise((resolve) => { + const reader = new FileReader() + reader.onloadend = () => { + if (reader.error) { + console.error("Error reading file:", reader.error) + resolve(null) + } else { + const result = reader.result + console.log("File read successfully", result) + resolve(typeof result === "string" ? result : null) + } + } + reader.readAsDataURL(file) + }) + }) + const imageDataArray = await Promise.all(imagePromises) + const dataUrls = imageDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null) + if (dataUrls.length > 0) { + setSelectedImages((prevImages) => [...prevImages, ...dataUrls].slice(0, MAX_IMAGES_PER_MESSAGE)) + if (typeof vscode !== 'undefined') { + vscode.postMessage({ + type: 'draggedImages', + dataUrls: dataUrls + }) + } + } else { + console.warn("No valid images were processed") + } + } + }} + onDragOver={(e) => { + e.preventDefault() + }} + > {showContextMenu && (
( onHeightChange?.(height) }} placeholder={placeholderText} - maxRows={10} + minRows={2} + maxRows={20} autoFocus={true} style={{ width: "100%", @@ -523,7 +581,6 @@ const ChatTextArea = forwardRef( resize: "none", overflowX: "hidden", overflowY: "scroll", - scrollbarWidth: "none", // Since we have maxRows, when text is long enough it starts to overflow the bottom padding, appearing behind the thumbnails. To fix this, we use a transparent border to push the text up instead. (https://stackoverflow.com/questions/42631947/maintaining-a-padding-inside-of-text-area/52538410#52538410) // borderTop: "9px solid transparent", borderLeft: 0, @@ -560,11 +617,11 @@ const ChatTextArea = forwardRef(
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 8dedae9e54..7a89951895 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -689,7 +689,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie useEvent("wheel", handleWheel, window, { passive: true }) // passive improves scrolling performance const placeholderText = useMemo(() => { - const text = task ? "Type a message (@ to add context)..." : "Type your task here (@ to add context)..." + const text = task ? "Type a message...\n(@ to add context, hold shift to drag in images)" : "Type your task here...\n(@ to add context, hold shift to drag in images)" return text }, [task]) diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index 76a0e190fa..bec38af56f 100644 --- a/webview-ui/src/components/history/HistoryView.tsx +++ b/webview-ui/src/components/history/HistoryView.tsx @@ -462,10 +462,13 @@ export const highlight = ( let i: number for (i = 0; i < pathValue.length - 1; i++) { + if (pathValue[i] === "__proto__" || pathValue[i] === "constructor") return obj = obj[pathValue[i]] as Record } - obj[pathValue[i]] = value + if (pathValue[i] !== "__proto__" && pathValue[i] !== "constructor") { + obj[pathValue[i]] = value + } } // Function to merge overlapping regions