mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-26 11:39:22 +00:00
Compare commits
12 Commits
feature/fa
...
feature/te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b17e116996 | ||
|
|
0af8b65407 | ||
|
|
db1e358081 | ||
|
|
a28bf81185 | ||
|
|
d1964200f9 | ||
|
|
42ab185890 | ||
|
|
b2bff47fc7 | ||
|
|
f1328b8437 | ||
|
|
54e41e3b31 | ||
|
|
c306cd89fc | ||
|
|
0414768cf8 | ||
|
|
055c0d8374 |
@@ -1,4 +1,4 @@
|
||||
name: Gemini Automated Issue Triage
|
||||
name: Qwen Automated Issue Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
triage-issue:
|
||||
timeout-minutes: 5
|
||||
if: ${{ github.repository == 'google-gemini/gemini-cli' }}
|
||||
if: ${{ github.repository == 'QwenLM/qwen-code' }}
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
@@ -17,22 +17,13 @@ jobs:
|
||||
cancel-in-progress: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Generate GitHub App Token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
|
||||
- name: Run Gemini Issue Triage
|
||||
uses: google-gemini/gemini-cli-action@df3f890f003d28c60a2a09d2c29e0126e4d1e2ff
|
||||
- name: Run Qwen Issue Triage
|
||||
uses: QwenLM/qwen-code-action@5fd6818d04d64e87d255ee4d5f77995e32fbf4c2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
version: 0.1.8-rc.0
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
OTLP_GCP_WIF_PROVIDER: ${{ secrets.OTLP_GCP_WIF_PROVIDER }}
|
||||
OTLP_GOOGLE_CLOUD_PROJECT: ${{ secrets.OTLP_GOOGLE_CLOUD_PROJECT }}
|
||||
version: 0.0.4
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
settings_json: |
|
||||
{
|
||||
"coreTools": [
|
||||
@@ -40,10 +31,6 @@ jobs:
|
||||
"run_shell_command(gh issue edit)",
|
||||
"run_shell_command(gh issue list)"
|
||||
],
|
||||
"telemetry": {
|
||||
"enabled": true,
|
||||
"target": "gcp"
|
||||
},
|
||||
"sandbox": false
|
||||
}
|
||||
prompt: |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Gemini Scheduled Issue Triage
|
||||
name: Qwen Scheduled Issue Triage
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -8,24 +8,17 @@ on:
|
||||
jobs:
|
||||
triage-issues:
|
||||
timeout-minutes: 10
|
||||
if: ${{ github.repository == 'google-gemini/gemini-cli' }}
|
||||
if: ${{ github.repository == 'QwenLM/qwen-code' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
issues: write
|
||||
steps:
|
||||
- name: Generate GitHub App Token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
|
||||
- name: Find untriaged issues
|
||||
id: find_issues
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "🔍 Finding issues without labels..."
|
||||
NO_LABEL_ISSUES=$(gh issue list --repo ${{ github.repository }} --search "is:open is:issue no:label" --json number,title,body)
|
||||
@@ -41,18 +34,18 @@ jobs:
|
||||
|
||||
echo "✅ Found $(echo "$ISSUES" | jq 'length') issues to triage! 🎯"
|
||||
|
||||
- name: Run Gemini Issue Triage
|
||||
- name: Run Qwen Issue Triage
|
||||
if: steps.find_issues.outputs.issues_to_triage != '[]'
|
||||
uses: google-gemini/gemini-cli-action@df3f890f003d28c60a2a09d2c29e0126e4d1e2ff
|
||||
uses: QwenLM/qwen-code-action@5fd6818d04d64e87d255ee4d5f77995e32fbf4c2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ISSUES_TO_TRIAGE: ${{ steps.find_issues.outputs.issues_to_triage }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
with:
|
||||
version: 0.1.8-rc.0
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
OTLP_GCP_WIF_PROVIDER: ${{ secrets.OTLP_GCP_WIF_PROVIDER }}
|
||||
OTLP_GOOGLE_CLOUD_PROJECT: ${{ secrets.OTLP_GOOGLE_CLOUD_PROJECT }}
|
||||
version: 0.0.4
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
OPENAI_MODEL: ${{ secrets.OPENAI_MODEL }}
|
||||
settings_json: |
|
||||
{
|
||||
"coreTools": [
|
||||
@@ -61,10 +54,6 @@ jobs:
|
||||
"run_shell_command(gh issue edit)",
|
||||
"run_shell_command(gh issue list)"
|
||||
],
|
||||
"telemetry": {
|
||||
"enabled": true,
|
||||
"target": "gcp"
|
||||
},
|
||||
"sandbox": false
|
||||
}
|
||||
prompt: |
|
||||
|
||||
13
.github/workflows/gemini-scheduled-pr-triage.yml
vendored
13
.github/workflows/gemini-scheduled-pr-triage.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Gemini Scheduled PR Triage 🚀
|
||||
name: Qwen Scheduled PR Triage 🚀
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
audit-prs:
|
||||
timeout-minutes: 15
|
||||
if: ${{ github.repository == 'google-gemini/gemini-cli' }}
|
||||
if: ${{ github.repository == 'QwenLM/qwen-code' }}
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
@@ -21,16 +21,9 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Generate GitHub App Token
|
||||
id: generate_token
|
||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
|
||||
- name: Run PR Triage Script
|
||||
id: run_triage
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: ./.github/scripts/pr-triage.sh
|
||||
|
||||
191
.github/workflows/qwen-code-pr-review.yml
vendored
Normal file
191
.github/workflows/qwen-code-pr-review.yml
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
name: 🧐 Qwen Pull Request Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to review'
|
||||
required: true
|
||||
type: number
|
||||
|
||||
jobs:
|
||||
review-pr:
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request' && github.event.action == 'opened') ||
|
||||
(github.event_name == 'issue_comment' &&
|
||||
github.event.issue.pull_request &&
|
||||
contains(github.event.comment.body, '@qwen /review') &&
|
||||
(github.event.comment.author_association == 'OWNER' ||
|
||||
github.event.comment.author_association == 'MEMBER' ||
|
||||
github.event.comment.author_association == 'COLLABORATOR')) ||
|
||||
(github.event_name == 'pull_request_review_comment' &&
|
||||
contains(github.event.comment.body, '@qwen /review') &&
|
||||
(github.event.comment.author_association == 'OWNER' ||
|
||||
github.event.comment.author_association == 'MEMBER' ||
|
||||
github.event.comment.author_association == 'COLLABORATOR')) ||
|
||||
(github.event_name == 'pull_request_review' &&
|
||||
contains(github.event.review.body, '@qwen /review') &&
|
||||
(github.event.review.author_association == 'OWNER' ||
|
||||
github.event.review.author_association == 'MEMBER' ||
|
||||
github.event.review.author_association == 'COLLABORATOR'))
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout PR code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get PR details (pull_request & workflow_dispatch)
|
||||
id: get_pr
|
||||
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
PR_NUMBER=${{ github.event.inputs.pr_number }}
|
||||
else
|
||||
PR_NUMBER=${{ github.event.pull_request.number }}
|
||||
fi
|
||||
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
|
||||
# Get PR details
|
||||
PR_DATA=$(gh pr view $PR_NUMBER --json title,body,additions,deletions,changedFiles,baseRefName,headRefName)
|
||||
echo "pr_data=$PR_DATA" >> "$GITHUB_OUTPUT"
|
||||
# Get file changes
|
||||
CHANGED_FILES=$(gh pr diff $PR_NUMBER --name-only)
|
||||
echo "changed_files<<EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "$CHANGED_FILES" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Get PR details (issue_comment)
|
||||
id: get_pr_comment
|
||||
if: github.event_name == 'issue_comment'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
run: |
|
||||
PR_NUMBER=${{ github.event.issue.number }}
|
||||
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
|
||||
# Extract additional instructions from comment
|
||||
ADDITIONAL_INSTRUCTIONS=$(echo "$COMMENT_BODY" | sed 's/.*@qwen \/review//' | xargs)
|
||||
echo "additional_instructions=$ADDITIONAL_INSTRUCTIONS" >> "$GITHUB_OUTPUT"
|
||||
# Get PR details
|
||||
PR_DATA=$(gh pr view $PR_NUMBER --json title,body,additions,deletions,changedFiles,baseRefName,headRefName)
|
||||
echo "pr_data=$PR_DATA" >> "$GITHUB_OUTPUT"
|
||||
# Get file changes
|
||||
CHANGED_FILES=$(gh pr diff $PR_NUMBER --name-only)
|
||||
echo "changed_files<<EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "$CHANGED_FILES" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Run Qwen PR Review
|
||||
uses: QwenLM/qwen-code-action@5fd6818d04d64e87d255ee4d5f77995e32fbf4c2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.get_pr.outputs.pr_number || steps.get_pr_comment.outputs.pr_number }}
|
||||
PR_DATA: ${{ steps.get_pr.outputs.pr_data || steps.get_pr_comment.outputs.pr_data }}
|
||||
CHANGED_FILES: ${{ steps.get_pr.outputs.changed_files || steps.get_pr_comment.outputs.changed_files }}
|
||||
ADDITIONAL_INSTRUCTIONS: ${{ steps.get_pr.outputs.additional_instructions || steps.get_pr_comment.outputs.additional_instructions }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
with:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
OPENAI_MODEL: ${{ secrets.OPENAI_MODEL }}
|
||||
settings_json: |
|
||||
{
|
||||
"coreTools": [
|
||||
"run_shell_command(echo)",
|
||||
"run_shell_command(gh pr view)",
|
||||
"run_shell_command(gh pr diff)",
|
||||
"run_shell_command(gh pr comment)",
|
||||
"run_shell_command(cat)",
|
||||
"run_shell_command(head)",
|
||||
"run_shell_command(tail)",
|
||||
"run_shell_command(grep)",
|
||||
"write_file"
|
||||
],
|
||||
"sandbox": false
|
||||
}
|
||||
prompt: |
|
||||
You are an expert code reviewer. You have access to shell commands to gather PR information and perform the review.
|
||||
|
||||
IMPORTANT: Use the available shell commands to gather information. Do not ask for information to be provided.
|
||||
|
||||
Start by running these commands to gather the required data:
|
||||
1. Run: echo "$PR_DATA" to get PR details (JSON format)
|
||||
2. Run: echo "$CHANGED_FILES" to get the list of changed files
|
||||
3. Run: echo "$PR_NUMBER" to get the PR number
|
||||
4. Run: echo "$ADDITIONAL_INSTRUCTIONS" to see any specific review instructions from the user
|
||||
5. Run: gh pr diff $PR_NUMBER to see the full diff
|
||||
6. For any specific files, use: cat filename, head -50 filename, or tail -50 filename
|
||||
|
||||
Additional Review Instructions:
|
||||
If ADDITIONAL_INSTRUCTIONS contains text, prioritize those specific areas or focus points in your review.
|
||||
Common instruction examples: "focus on security", "check performance", "review error handling", "check for breaking changes"
|
||||
|
||||
Once you have the information, provide a comprehensive code review by:
|
||||
1. Writing your review to a file: write_file("review.md", "<your detailed review feedback here>")
|
||||
2. Posting the review: gh pr comment $PR_NUMBER --body-file review.md --repo $REPOSITORY
|
||||
|
||||
Review Areas:
|
||||
- **Security**: Authentication, authorization, input validation, data sanitization
|
||||
- **Performance**: Algorithms, database queries, caching, resource usage
|
||||
- **Reliability**: Error handling, logging, testing coverage, edge cases
|
||||
- **Maintainability**: Code structure, documentation, naming conventions
|
||||
- **Functionality**: Logic correctness, requirements fulfillment
|
||||
|
||||
Output Format:
|
||||
Structure your review using this exact format with markdown:
|
||||
|
||||
## 📋 Review Summary
|
||||
Provide a brief 2-3 sentence overview of the PR and overall assessment.
|
||||
|
||||
## 🔍 General Feedback
|
||||
- List general observations about code quality
|
||||
- Mention overall patterns or architectural decisions
|
||||
- Highlight positive aspects of the implementation
|
||||
- Note any recurring themes across files
|
||||
|
||||
## 🎯 Specific Feedback
|
||||
Only include sections below that have actual issues. If there are no issues in a priority category, omit that entire section.
|
||||
|
||||
### 🔴 Critical
|
||||
(Only include this section if there are critical issues)
|
||||
Issues that must be addressed before merging (security vulnerabilities, breaking changes, major bugs):
|
||||
- **File: `filename:line`** - Description of critical issue with specific recommendation
|
||||
|
||||
### 🟡 High
|
||||
(Only include this section if there are high priority issues)
|
||||
Important issues that should be addressed (performance problems, design flaws, significant bugs):
|
||||
- **File: `filename:line`** - Description of high priority issue with suggested fix
|
||||
|
||||
### 🟢 Medium
|
||||
(Only include this section if there are medium priority issues)
|
||||
Improvements that would enhance code quality (style issues, minor optimizations, better practices):
|
||||
- **File: `filename:line`** - Description of medium priority improvement
|
||||
|
||||
### 🔵 Low
|
||||
(Only include this section if there are suggestions)
|
||||
Nice-to-have improvements and suggestions (documentation, naming, minor refactoring):
|
||||
- **File: `filename:line`** - Description of suggestion or enhancement
|
||||
|
||||
**Note**: If no specific issues are found in any category, simply state "No specific issues identified in this review."
|
||||
|
||||
## ✅ Highlights
|
||||
(Only include this section if there are positive aspects to highlight)
|
||||
- Mention specific good practices or implementations
|
||||
- Acknowledge well-written code sections
|
||||
- Note improvements from previous versions
|
||||
@@ -1,4 +1,4 @@
|
||||
# Gemini CLI Roadmap
|
||||
# Qwen Code Roadmap
|
||||
|
||||
The [Official Gemini CLI Roadmap](https://github.com/orgs/google-gemini/projects/11/)
|
||||
|
||||
|
||||
15
package-lock.json
generated
15
package-lock.json
generated
@@ -1,18 +1,15 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"tiktoken": "^1.0.21"
|
||||
},
|
||||
"bin": {
|
||||
"qwen": "bundle/gemini.js"
|
||||
},
|
||||
@@ -11771,7 +11768,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"dependencies": {
|
||||
"@google/genai": "1.9.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
@@ -11797,6 +11794,7 @@
|
||||
"string-width": "^7.1.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"strip-json-comments": "^3.1.1",
|
||||
"tiktoken": "^1.0.21",
|
||||
"update-notifier": "^7.3.1",
|
||||
"yargs": "^17.7.2",
|
||||
"zod": "^3.23.8"
|
||||
@@ -11846,7 +11844,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@qwen-code/qwen-code-core",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"dependencies": {
|
||||
"@google/genai": "1.9.0",
|
||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||
@@ -11872,6 +11870,7 @@
|
||||
"shell-quote": "^1.8.3",
|
||||
"simple-git": "^3.28.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"tiktoken": "^1.0.21",
|
||||
"undici": "^7.10.0",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
@@ -11912,7 +11911,7 @@
|
||||
},
|
||||
"packages/vscode-ide-companion": {
|
||||
"name": "qwen-code-vscode-ide-companion",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
@@ -13,7 +13,7 @@
|
||||
"url": "git+https://github.com/QwenLM/qwen-code.git"
|
||||
},
|
||||
"config": {
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.3"
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js",
|
||||
@@ -84,8 +84,5 @@
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vitest": "^3.2.4",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tiktoken": "^1.0.21"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "Qwen Code",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,7 +25,7 @@
|
||||
"dist"
|
||||
],
|
||||
"config": {
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.3"
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@qwen-code/qwen-code-core": "file:../core",
|
||||
@@ -54,7 +54,8 @@
|
||||
"strip-json-comments": "^3.1.1",
|
||||
"update-notifier": "^7.3.1",
|
||||
"yargs": "^17.7.2",
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.23.8",
|
||||
"tiktoken": "^1.0.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
|
||||
@@ -98,7 +98,6 @@ describe('Settings Loading and Merging', () => {
|
||||
expect(settings.merged).toEqual({
|
||||
customThemes: {},
|
||||
mcpServers: {},
|
||||
openaiConfig: {},
|
||||
});
|
||||
expect(settings.errors.length).toBe(0);
|
||||
});
|
||||
@@ -132,7 +131,6 @@ describe('Settings Loading and Merging', () => {
|
||||
...systemSettingsContent,
|
||||
customThemes: {},
|
||||
mcpServers: {},
|
||||
openaiConfig: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,7 +164,6 @@ describe('Settings Loading and Merging', () => {
|
||||
...userSettingsContent,
|
||||
customThemes: {},
|
||||
mcpServers: {},
|
||||
openaiConfig: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -198,7 +195,6 @@ describe('Settings Loading and Merging', () => {
|
||||
...workspaceSettingsContent,
|
||||
customThemes: {},
|
||||
mcpServers: {},
|
||||
openaiConfig: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -236,7 +232,6 @@ describe('Settings Loading and Merging', () => {
|
||||
contextFileName: 'WORKSPACE_CONTEXT.md',
|
||||
customThemes: {},
|
||||
mcpServers: {},
|
||||
openaiConfig: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -286,7 +281,6 @@ describe('Settings Loading and Merging', () => {
|
||||
allowMCPServers: ['server1', 'server2'],
|
||||
customThemes: {},
|
||||
mcpServers: {},
|
||||
openaiConfig: {},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -566,7 +560,6 @@ describe('Settings Loading and Merging', () => {
|
||||
expect(settings.merged).toEqual({
|
||||
customThemes: {},
|
||||
mcpServers: {},
|
||||
openaiConfig: {},
|
||||
});
|
||||
|
||||
// Check that error objects are populated in settings.errors
|
||||
@@ -961,7 +954,6 @@ describe('Settings Loading and Merging', () => {
|
||||
...systemSettingsContent,
|
||||
customThemes: {},
|
||||
mcpServers: {},
|
||||
openaiConfig: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +79,6 @@ export interface Settings {
|
||||
checkpointing?: CheckpointingSettings;
|
||||
autoConfigureMaxOldSpaceSize?: boolean;
|
||||
enableOpenAILogging?: boolean;
|
||||
openaiConfig?: Record<string, string>;
|
||||
|
||||
// Git-aware file filtering settings
|
||||
fileFiltering?: {
|
||||
@@ -175,11 +174,6 @@ export class LoadedSettings {
|
||||
...(workspace.mcpServers || {}),
|
||||
...(system.mcpServers || {}),
|
||||
},
|
||||
openaiConfig: {
|
||||
...(user.openaiConfig || {}),
|
||||
...(workspace.openaiConfig || {}),
|
||||
...(system.openaiConfig || {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -313,30 +307,6 @@ export function loadEnvironment(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads OpenAI configuration from user settings as fallback.
|
||||
* Priority order: env vars > .env file > workspace settings.json > ~/.qwen/settings.json
|
||||
*/
|
||||
export function loadOpenAIConfigFromSettings(settings: Settings): void {
|
||||
if (!settings.openaiConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only set environment variables if they're not already set
|
||||
// This maintains the priority order
|
||||
if (!process.env.OPENAI_API_KEY && settings.openaiConfig.OPENAI_API_KEY) {
|
||||
process.env.OPENAI_API_KEY = settings.openaiConfig.OPENAI_API_KEY;
|
||||
}
|
||||
|
||||
if (!process.env.OPENAI_BASE_URL && settings.openaiConfig.OPENAI_BASE_URL) {
|
||||
process.env.OPENAI_BASE_URL = settings.openaiConfig.OPENAI_BASE_URL;
|
||||
}
|
||||
|
||||
if (!process.env.OPENAI_MODEL && settings.openaiConfig.OPENAI_MODEL) {
|
||||
process.env.OPENAI_MODEL = settings.openaiConfig.OPENAI_MODEL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads settings from user and workspace directories.
|
||||
* Project settings override user settings.
|
||||
@@ -416,7 +386,7 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
|
||||
});
|
||||
}
|
||||
|
||||
const loadedSettings = new LoadedSettings(
|
||||
return new LoadedSettings(
|
||||
{
|
||||
path: systemSettingsPath,
|
||||
settings: systemSettings,
|
||||
@@ -431,12 +401,6 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
|
||||
},
|
||||
settingsErrors,
|
||||
);
|
||||
|
||||
// Load OpenAI config from settings as fallback
|
||||
// Priority order: env vars > .env file > workspace settings > user settings
|
||||
loadOpenAIConfigFromSettings(loadedSettings.merged);
|
||||
|
||||
return loadedSettings;
|
||||
}
|
||||
|
||||
export function saveSettings(settingsFile: SettingsFile): void {
|
||||
|
||||
@@ -92,22 +92,6 @@ export function AuthDialog({
|
||||
setOpenAIApiKey(apiKey);
|
||||
setOpenAIBaseUrl(baseUrl);
|
||||
setOpenAIModel(model);
|
||||
|
||||
// Save OpenAI configuration to user settings as fallback
|
||||
// Priority order: .env > workspace settings.json > ~/.qwen/settings.json
|
||||
try {
|
||||
const openAIConfig: { [key: string]: string } = {};
|
||||
if (apiKey.trim()) openAIConfig.OPENAI_API_KEY = apiKey.trim();
|
||||
if (baseUrl.trim()) openAIConfig.OPENAI_BASE_URL = baseUrl.trim();
|
||||
if (model.trim()) openAIConfig.OPENAI_MODEL = model.trim();
|
||||
|
||||
// Save to user settings as environment variables for next time
|
||||
settings.setValue(SettingScope.User, 'openaiConfig', openAIConfig);
|
||||
} catch (error) {
|
||||
// Don't block authentication if saving fails
|
||||
console.warn('Failed to save OpenAI config to settings:', error);
|
||||
}
|
||||
|
||||
setShowOpenAIKeyPrompt(false);
|
||||
onSelect(AuthType.USE_OPENAI, SettingScope.User);
|
||||
};
|
||||
@@ -140,27 +124,10 @@ export function AuthDialog({
|
||||
});
|
||||
|
||||
if (showOpenAIKeyPrompt) {
|
||||
// Load default values from settings
|
||||
const defaultValues = {
|
||||
apiKey:
|
||||
process.env.OPENAI_API_KEY ||
|
||||
settings.merged.openaiConfig?.OPENAI_API_KEY ||
|
||||
'',
|
||||
baseUrl:
|
||||
process.env.OPENAI_BASE_URL ||
|
||||
settings.merged.openaiConfig?.OPENAI_BASE_URL ||
|
||||
'',
|
||||
model:
|
||||
process.env.OPENAI_MODEL ||
|
||||
settings.merged.openaiConfig?.OPENAI_MODEL ||
|
||||
'',
|
||||
};
|
||||
|
||||
return (
|
||||
<OpenAIKeyPrompt
|
||||
onSubmit={handleOpenAIKeySubmit}
|
||||
onCancel={handleOpenAIKeyCancel}
|
||||
defaultValues={defaultValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -198,7 +165,7 @@ export function AuthDialog({
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<Text color={Colors.AccentBlue}>
|
||||
{'https://github.com/QwenLM/qwen-code/blob/main/README.md'}
|
||||
{'https://github.com/QwenLM/Qwen3-Coder/blob/main/README.md'}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -17,7 +17,8 @@ describe('OpenAIKeyPrompt', () => {
|
||||
<OpenAIKeyPrompt onSubmit={onSubmit} onCancel={onCancel} />,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toContain('API Configuration Required');
|
||||
expect(lastFrame()).toContain('OpenAI Configuration Required');
|
||||
expect(lastFrame()).toContain('https://platform.openai.com/api-keys');
|
||||
expect(lastFrame()).toContain(
|
||||
'Press Enter to continue, Tab/↑↓ to navigate, Esc to cancel',
|
||||
);
|
||||
@@ -32,7 +33,7 @@ describe('OpenAIKeyPrompt', () => {
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('API Configuration Required');
|
||||
expect(output).toContain('OpenAI Configuration Required');
|
||||
expect(output).toContain('API Key:');
|
||||
expect(output).toContain('Base URL:');
|
||||
expect(output).toContain('Model:');
|
||||
|
||||
@@ -11,77 +11,63 @@ import { Colors } from '../colors.js';
|
||||
interface OpenAIKeyPromptProps {
|
||||
onSubmit: (apiKey: string, baseUrl: string, model: string) => void;
|
||||
onCancel: () => void;
|
||||
defaultValues?: {
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
model?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function OpenAIKeyPrompt({
|
||||
onSubmit,
|
||||
onCancel,
|
||||
defaultValues,
|
||||
}: OpenAIKeyPromptProps): React.JSX.Element {
|
||||
const [apiKey, setApiKey] = useState(defaultValues?.apiKey || '');
|
||||
const [baseUrl, setBaseUrl] = useState(defaultValues?.baseUrl || '');
|
||||
const [model, setModel] = useState(defaultValues?.model || '');
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [baseUrl, setBaseUrl] = useState('');
|
||||
const [model, setModel] = useState('');
|
||||
const [currentField, setCurrentField] = useState<
|
||||
'apiKey' | 'baseUrl' | 'model'
|
||||
>('apiKey');
|
||||
|
||||
useInput((input, key) => {
|
||||
// Ignore control sequences like [I or [O from focus switching
|
||||
if (input && (input === '[I' || input === '[O')) {
|
||||
// 过滤粘贴相关的控制序列
|
||||
let cleanInput = (input || '')
|
||||
// 过滤 ESC 开头的控制序列(如 \u001b[200~、\u001b[201~ 等)
|
||||
.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '') // eslint-disable-line no-control-regex
|
||||
// 过滤粘贴开始标记 [200~
|
||||
.replace(/\[200~/g, '')
|
||||
// 过滤粘贴结束标记 [201~
|
||||
.replace(/\[201~/g, '')
|
||||
// 过滤单独的 [ 和 ~ 字符(可能是粘贴标记的残留)
|
||||
.replace(/^\[|~$/g, '');
|
||||
|
||||
// 再过滤所有不可见字符(ASCII < 32,除了回车换行)
|
||||
cleanInput = cleanInput
|
||||
.split('')
|
||||
.filter((ch) => ch.charCodeAt(0) >= 32)
|
||||
.join('');
|
||||
|
||||
if (cleanInput.length > 0) {
|
||||
if (currentField === 'apiKey') {
|
||||
setApiKey((prev) => prev + cleanInput);
|
||||
} else if (currentField === 'baseUrl') {
|
||||
setBaseUrl((prev) => prev + cleanInput);
|
||||
} else if (currentField === 'model') {
|
||||
setModel((prev) => prev + cleanInput);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理字符输入
|
||||
if (input && input.length > 0) {
|
||||
// Filter paste-related control sequences
|
||||
let cleanInput = (input || '')
|
||||
// Filter ESC-based control sequences (like \u001b[200~, \u001b[201~, etc.)
|
||||
.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '') // eslint-disable-line no-control-regex
|
||||
// Filter paste start marker [200~
|
||||
.replace(/\[200~/g, '')
|
||||
// Filter paste end marker [201~
|
||||
.replace(/\[201~/g, '')
|
||||
// Filter standalone [ and ~ characters (possible paste marker remnants)
|
||||
.replace(/^\[|~$/g, '');
|
||||
|
||||
// Filter all invisible characters (ASCII < 32, except newlines)
|
||||
cleanInput = cleanInput
|
||||
.split('')
|
||||
.filter((ch) => ch.charCodeAt(0) >= 32)
|
||||
.join('');
|
||||
|
||||
if (cleanInput.length > 0) {
|
||||
if (currentField === 'apiKey') {
|
||||
setApiKey((prev) => prev + cleanInput);
|
||||
} else if (currentField === 'baseUrl') {
|
||||
setBaseUrl((prev) => prev + cleanInput);
|
||||
} else if (currentField === 'model') {
|
||||
setModel((prev) => prev + cleanInput);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if Enter key was pressed (by checking for newline characters)
|
||||
// 检查是否是 Enter 键(通过检查输入是否包含换行符)
|
||||
if (input.includes('\n') || input.includes('\r')) {
|
||||
if (currentField === 'apiKey') {
|
||||
// Allow empty API key to jump to next field, user can return to modify later
|
||||
// 允许空 API key 跳转到下一个字段,让用户稍后可以返回修改
|
||||
setCurrentField('baseUrl');
|
||||
return;
|
||||
} else if (currentField === 'baseUrl') {
|
||||
setCurrentField('model');
|
||||
return;
|
||||
} else if (currentField === 'model') {
|
||||
// Only check if API key is empty when submitting
|
||||
// 只有在提交时才检查 API key 是否为空
|
||||
if (apiKey.trim()) {
|
||||
onSubmit(apiKey.trim(), baseUrl.trim(), model.trim());
|
||||
} else {
|
||||
// If API key is empty, return to API key field
|
||||
// 如果 API key 为空,回到 API key 字段
|
||||
setCurrentField('apiKey');
|
||||
}
|
||||
}
|
||||
@@ -146,10 +132,15 @@ export function OpenAIKeyPrompt({
|
||||
width="100%"
|
||||
>
|
||||
<Text bold color={Colors.AccentBlue}>
|
||||
API Configuration Required
|
||||
OpenAI Configuration Required
|
||||
</Text>
|
||||
<Box marginTop={1}>
|
||||
<Text>Please enter your API configuration.</Text>
|
||||
<Text>
|
||||
Please enter your OpenAI configuration. You can get an API key from{' '}
|
||||
<Text color={Colors.AccentBlue}>
|
||||
https://platform.openai.com/api-keys
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginTop={1} flexDirection="row">
|
||||
<Box width={12}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code-core",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "Qwen Code Core",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -45,7 +45,8 @@
|
||||
"simple-git": "^3.28.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"undici": "^7.10.0",
|
||||
"ws": "^8.18.0"
|
||||
"ws": "^8.18.0",
|
||||
"tiktoken": "^1.0.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/diff": "^7.0.2",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "qwen-code-vscode-ide-companion",
|
||||
"displayName": "Qwen Code Companion",
|
||||
"description": "Enable Qwen Code with direct access to your VS Code workspace.",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"publisher": "qwenlm",
|
||||
"icon": "assets/icon.png",
|
||||
"repository": {
|
||||
|
||||
Reference in New Issue
Block a user