mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-01-08 09:59:14 +00:00
Compare commits
53 Commits
sdk-typesc
...
qwencode-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4eb3adea8 | ||
|
|
7dc7c6380d | ||
|
|
d2d2b845c5 | ||
|
|
96080f84a6 | ||
|
|
2b6218e564 | ||
|
|
24edf32da8 | ||
|
|
51b08f700c | ||
|
|
32e8b01cf0 | ||
|
|
db9d5cb45d | ||
|
|
73848d3867 | ||
|
|
6a62167f79 | ||
|
|
6ff437671e | ||
|
|
30f9e9c782 | ||
|
|
e4caa7a856 | ||
|
|
ac7ba95d65 | ||
|
|
4154493640 | ||
|
|
422998d7f0 | ||
|
|
68628bf952 | ||
|
|
e5efad89e0 | ||
|
|
e09bb5f5c0 | ||
|
|
24d11179d8 | ||
|
|
2ef8b6f350 | ||
|
|
5779f7ab1d | ||
|
|
642dda0315 | ||
|
|
bbbdeb280d | ||
|
|
0d43ddee2a | ||
|
|
50e03f2dd6 | ||
|
|
f440ff2f7f | ||
|
|
9a6b0abc37 | ||
|
|
00547ba439 | ||
|
|
fc1dac9dc7 | ||
|
|
338eb9038d | ||
|
|
e0b9044833 | ||
|
|
f33f43e2f7 | ||
|
|
4e7929850c | ||
|
|
9cc5c3ed8f | ||
|
|
a92be72e88 | ||
|
|
52cd1da4a7 | ||
|
|
c5c556a326 | ||
|
|
a8a863581b | ||
|
|
80bb2890df | ||
|
|
abd9ee2a7b | ||
|
|
b8df689e31 | ||
|
|
e610578ecc | ||
|
|
235159216e | ||
|
|
93b30cca29 | ||
|
|
f9da1b819e | ||
|
|
59c3d3d0f9 | ||
|
|
2f0fa267c8 | ||
|
|
fa6ae0a324 | ||
|
|
387be44866 | ||
|
|
51b82771da | ||
|
|
629cd14fad |
9
.github/workflows/e2e.yml
vendored
9
.github/workflows/e2e.yml
vendored
@@ -18,8 +18,6 @@ jobs:
|
||||
- 'sandbox:docker'
|
||||
node-version:
|
||||
- '20.x'
|
||||
- '22.x'
|
||||
- '24.x'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
@@ -67,10 +65,13 @@ jobs:
|
||||
OPENAI_BASE_URL: '${{ secrets.OPENAI_BASE_URL }}'
|
||||
OPENAI_MODEL: '${{ secrets.OPENAI_MODEL }}'
|
||||
KEEP_OUTPUT: 'true'
|
||||
SANDBOX: '${{ matrix.sandbox }}'
|
||||
VERBOSE: 'true'
|
||||
run: |-
|
||||
npm run "test:integration:${SANDBOX}"
|
||||
if [[ "${{ matrix.sandbox }}" == "sandbox:docker" ]]; then
|
||||
npm run test:integration:sandbox:docker
|
||||
else
|
||||
npm run test:integration:sandbox:none
|
||||
fi
|
||||
|
||||
e2e-test-macos:
|
||||
name: 'E2E Test - macOS'
|
||||
|
||||
110
CONTRIBUTING.md
110
CONTRIBUTING.md
@@ -2,27 +2,6 @@
|
||||
|
||||
We would love to accept your patches and contributions to this project.
|
||||
|
||||
## Before you begin
|
||||
|
||||
### Sign our Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a
|
||||
[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
|
||||
You (or your employer) retain the copyright to your contribution; this simply
|
||||
gives us permission to use and redistribute your contributions as part of the
|
||||
project.
|
||||
|
||||
If you or your current employer have already signed the Google CLA (even if it
|
||||
was for a different project), you probably don't need to do it again.
|
||||
|
||||
Visit <https://cla.developers.google.com/> to see your current agreements or to
|
||||
sign a new one.
|
||||
|
||||
### Review our Community Guidelines
|
||||
|
||||
This project follows [Google's Open Source Community
|
||||
Guidelines](https://opensource.google/conduct/).
|
||||
|
||||
## Contribution Process
|
||||
|
||||
### Code Reviews
|
||||
@@ -74,12 +53,6 @@ Your PR should have a clear, descriptive title and a detailed description of the
|
||||
|
||||
In the PR description, explain the "why" behind your changes and link to the relevant issue (e.g., `Fixes #123`).
|
||||
|
||||
## Forking
|
||||
|
||||
If you are forking the repository you will be able to run the Build, Test and Integration test workflows. However in order to make the integration tests run you'll need to add a [GitHub Repository Secret](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository) with a value of `GEMINI_API_KEY` and set that to a valid API key that you have available. Your key and secret are private to your repo; no one without access can see your key and you cannot see any secrets related to this repo.
|
||||
|
||||
Additionally you will need to click on the `Actions` tab and enable workflows for your repository, you'll find it's the large blue button in the center of the screen.
|
||||
|
||||
## Development Setup and Workflow
|
||||
|
||||
This section guides contributors on how to build, modify, and understand the development setup of this project.
|
||||
@@ -98,8 +71,8 @@ This section guides contributors on how to build, modify, and understand the dev
|
||||
To clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/google-gemini/gemini-cli.git # Or your fork's URL
|
||||
cd gemini-cli
|
||||
git clone https://github.com/QwenLM/qwen-code.git # Or your fork's URL
|
||||
cd qwen-code
|
||||
```
|
||||
|
||||
To install dependencies defined in `package.json` as well as root dependencies:
|
||||
@@ -118,9 +91,9 @@ This command typically compiles TypeScript to JavaScript, bundles assets, and pr
|
||||
|
||||
### Enabling Sandboxing
|
||||
|
||||
[Sandboxing](#sandboxing) is highly recommended and requires, at a minimum, setting `GEMINI_SANDBOX=true` in your `~/.env` and ensuring a sandboxing provider (e.g. `macOS Seatbelt`, `docker`, or `podman`) is available. See [Sandboxing](#sandboxing) for details.
|
||||
[Sandboxing](#sandboxing) is highly recommended and requires, at a minimum, setting `QWEN_SANDBOX=true` in your `~/.env` and ensuring a sandboxing provider (e.g. `macOS Seatbelt`, `docker`, or `podman`) is available. See [Sandboxing](#sandboxing) for details.
|
||||
|
||||
To build both the `gemini` CLI utility and the sandbox container, run `build:all` from the root directory:
|
||||
To build both the `qwen-code` CLI utility and the sandbox container, run `build:all` from the root directory:
|
||||
|
||||
```bash
|
||||
npm run build:all
|
||||
@@ -130,13 +103,13 @@ To skip building the sandbox container, you can use `npm run build` instead.
|
||||
|
||||
### Running
|
||||
|
||||
To start the Gemini CLI from the source code (after building), run the following command from the root directory:
|
||||
To start the Qwen Code application from the source code (after building), run the following command from the root directory:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
If you'd like to run the source build outside of the gemini-cli folder, you can utilize `npm link path/to/gemini-cli/packages/cli` (see: [docs](https://docs.npmjs.com/cli/v9/commands/npm-link)) or `alias gemini="node path/to/gemini-cli/packages/cli"` to run with `gemini`
|
||||
If you'd like to run the source build outside of the qwen-code folder, you can utilize `npm link path/to/qwen-code/packages/cli` (see: [docs](https://docs.npmjs.com/cli/v9/commands/npm-link)) to run with `qwen-code`
|
||||
|
||||
### Running Tests
|
||||
|
||||
@@ -154,7 +127,7 @@ This will run tests located in the `packages/core` and `packages/cli` directorie
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
The integration tests are designed to validate the end-to-end functionality of the Gemini CLI. They are not run as part of the default `npm run test` command.
|
||||
The integration tests are designed to validate the end-to-end functionality of Qwen Code. They are not run as part of the default `npm run test` command.
|
||||
|
||||
To run the integration tests, use the following command:
|
||||
|
||||
@@ -209,19 +182,61 @@ npm run lint
|
||||
### Coding Conventions
|
||||
|
||||
- Please adhere to the coding style, patterns, and conventions used throughout the existing codebase.
|
||||
- Consult [QWEN.md](https://github.com/QwenLM/qwen-code/blob/main/QWEN.md) (typically found in the project root) for specific instructions related to AI-assisted development, including conventions for React, comments, and Git usage.
|
||||
- **Imports:** Pay special attention to import paths. The project uses ESLint to enforce restrictions on relative imports between packages.
|
||||
|
||||
### Project Structure
|
||||
|
||||
- `packages/`: Contains the individual sub-packages of the project.
|
||||
- `cli/`: The command-line interface.
|
||||
- `core/`: The core backend logic for the Gemini CLI.
|
||||
- `core/`: The core backend logic for Qwen Code.
|
||||
- `docs/`: Contains all project documentation.
|
||||
- `scripts/`: Utility scripts for building, testing, and development tasks.
|
||||
|
||||
For more detailed architecture, see `docs/architecture.md`.
|
||||
|
||||
## Documentation Development
|
||||
|
||||
This section describes how to develop and preview the documentation locally.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. Ensure you have Node.js (version 18+) installed
|
||||
2. Have npm or yarn available
|
||||
|
||||
### Setup Documentation Site Locally
|
||||
|
||||
To work on the documentation and preview changes locally:
|
||||
|
||||
1. Navigate to the `docs-site` directory:
|
||||
|
||||
```bash
|
||||
cd docs-site
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Link the documentation content from the main `docs` directory:
|
||||
|
||||
```bash
|
||||
npm run link
|
||||
```
|
||||
|
||||
This creates a symbolic link from `../docs` to `content` in the docs-site project, allowing the documentation content to be served by the Next.js site.
|
||||
|
||||
4. Start the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
5. Open [http://localhost:3000](http://localhost:3000) in your browser to see the documentation site with live updates as you make changes.
|
||||
|
||||
Any changes made to the documentation files in the main `docs` directory will be reflected immediately in the documentation site.
|
||||
|
||||
## Debugging
|
||||
|
||||
### VS Code:
|
||||
@@ -231,7 +246,7 @@ For more detailed architecture, see `docs/architecture.md`.
|
||||
```bash
|
||||
npm run debug
|
||||
```
|
||||
This command runs `node --inspect-brk dist/gemini.js` within the `packages/cli` directory, pausing execution until a debugger attaches. You can then open `chrome://inspect` in your Chrome browser to connect to the debugger.
|
||||
This command runs `node --inspect-brk dist/index.js` within the `packages/cli` directory, pausing execution until a debugger attaches. You can then open `chrome://inspect` in your Chrome browser to connect to the debugger.
|
||||
2. In VS Code, use the "Attach" launch configuration (found in `.vscode/launch.json`).
|
||||
|
||||
Alternatively, you can use the "Launch Program" configuration in VS Code if you prefer to launch the currently open file directly, but 'F5' is generally recommended.
|
||||
@@ -239,16 +254,16 @@ Alternatively, you can use the "Launch Program" configuration in VS Code if you
|
||||
To hit a breakpoint inside the sandbox container run:
|
||||
|
||||
```bash
|
||||
DEBUG=1 gemini
|
||||
DEBUG=1 qwen-code
|
||||
```
|
||||
|
||||
**Note:** If you have `DEBUG=true` in a project's `.env` file, it won't affect gemini-cli due to automatic exclusion. Use `.gemini/.env` files for gemini-cli specific debug settings.
|
||||
**Note:** If you have `DEBUG=true` in a project's `.env` file, it won't affect qwen-code due to automatic exclusion. Use `.qwen-code/.env` files for qwen-code specific debug settings.
|
||||
|
||||
### React DevTools
|
||||
|
||||
To debug the CLI's React-based UI, you can use React DevTools. Ink, the library used for the CLI's interface, is compatible with React DevTools version 4.x.
|
||||
|
||||
1. **Start the Gemini CLI in development mode:**
|
||||
1. **Start the Qwen Code application in development mode:**
|
||||
|
||||
```bash
|
||||
DEV=true npm start
|
||||
@@ -270,23 +285,10 @@ To debug the CLI's React-based UI, you can use React DevTools. Ink, the library
|
||||
```
|
||||
|
||||
Your running CLI application should then connect to React DevTools.
|
||||

|
||||
|
||||
## Sandboxing
|
||||
|
||||
### macOS Seatbelt
|
||||
|
||||
On macOS, `qwen` uses Seatbelt (`sandbox-exec`) under a `permissive-open` profile (see `packages/cli/src/utils/sandbox-macos-permissive-open.sb`) that restricts writes to the project folder but otherwise allows all other operations and outbound network traffic ("open") by default. You can switch to a `restrictive-closed` profile (see `packages/cli/src/utils/sandbox-macos-restrictive-closed.sb`) that declines all operations and outbound network traffic ("closed") by default by setting `SEATBELT_PROFILE=restrictive-closed` in your environment or `.env` file. Available built-in profiles are `{permissive,restrictive}-{open,closed,proxied}` (see below for proxied networking). You can also switch to a custom profile `SEATBELT_PROFILE=<profile>` if you also create a file `.qwen/sandbox-macos-<profile>.sb` under your project settings directory `.qwen`.
|
||||
|
||||
### Container-based Sandboxing (All Platforms)
|
||||
|
||||
For stronger container-based sandboxing on macOS or other platforms, you can set `GEMINI_SANDBOX=true|docker|podman|<command>` in your environment or `.env` file. The specified command (or if `true` then either `docker` or `podman`) must be installed on the host machine. Once enabled, `npm run build:all` will build a minimal container ("sandbox") image and `npm start` will launch inside a fresh instance of that container. The first build can take 20-30s (mostly due to downloading of the base image) but after that both build and start overhead should be minimal. Default builds (`npm run build`) will not rebuild the sandbox.
|
||||
|
||||
Container-based sandboxing mounts the project directory (and system temp directory) with read-write access and is started/stopped/removed automatically as you start/stop Gemini CLI. Files created within the sandbox should be automatically mapped to your user/group on host machine. You can easily specify additional mounts, ports, or environment variables by setting `SANDBOX_{MOUNTS,PORTS,ENV}` as needed. You can also fully customize the sandbox for your projects by creating the files `.qwen/sandbox.Dockerfile` and/or `.qwen/sandbox.bashrc` under your project settings directory (`.qwen`) and running `qwen` with `BUILD_SANDBOX=1` to trigger building of your custom sandbox.
|
||||
|
||||
#### Proxied Networking
|
||||
|
||||
All sandboxing methods, including macOS Seatbelt using `*-proxied` profiles, support restricting outbound network traffic through a custom proxy server that can be specified as `GEMINI_SANDBOX_PROXY_COMMAND=<command>`, where `<command>` must start a proxy server that listens on `:::8877` for relevant requests. See `docs/examples/proxy-script.md` for a minimal proxy that only allows `HTTPS` connections to `example.com:443` (e.g. `curl https://example.com`) and declines all other requests. The proxy is started and stopped automatically alongside the sandbox.
|
||||
> TBD
|
||||
|
||||
## Manual Publish
|
||||
|
||||
|
||||
10
Makefile
10
Makefile
@@ -1,9 +1,9 @@
|
||||
# Makefile for gemini-cli
|
||||
# Makefile for qwen-code
|
||||
|
||||
.PHONY: help install build build-sandbox build-all test lint format preflight clean start debug release run-npx create-alias
|
||||
|
||||
help:
|
||||
@echo "Makefile for gemini-cli"
|
||||
@echo "Makefile for qwen-code"
|
||||
@echo ""
|
||||
@echo "Usage:"
|
||||
@echo " make install - Install npm dependencies"
|
||||
@@ -14,11 +14,11 @@ help:
|
||||
@echo " make format - Format the code"
|
||||
@echo " make preflight - Run formatting, linting, and tests"
|
||||
@echo " make clean - Remove generated files"
|
||||
@echo " make start - Start the Gemini CLI"
|
||||
@echo " make debug - Start the Gemini CLI in debug mode"
|
||||
@echo " make start - Start the Qwen Code CLI"
|
||||
@echo " make debug - Start the Qwen Code CLI in debug mode"
|
||||
@echo ""
|
||||
@echo " make run-npx - Run the CLI using npx (for testing the published package)"
|
||||
@echo " make create-alias - Create a 'gemini' alias for your shell"
|
||||
@echo " make create-alias - Create a 'qwen' alias for your shell"
|
||||
|
||||
install:
|
||||
npm install
|
||||
|
||||
410
README.md
410
README.md
@@ -1,382 +1,152 @@
|
||||
# Qwen Code
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
[](https://www.npmjs.com/package/@qwen-code/qwen-code)
|
||||
[](./LICENSE)
|
||||
[](https://nodejs.org/)
|
||||
[](https://www.npmjs.com/package/@qwen-code/qwen-code)
|
||||
|
||||
**AI-powered command-line workflow tool for developers**
|
||||
**An open-source AI agent that lives in your terminal.**
|
||||
|
||||
[Installation](#installation) • [Quick Start](#quick-start) • [Features](#key-features) • [Documentation](./docs/) • [Contributing](./CONTRIBUTING.md)
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/zh/users/overview">中文</a> |
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/de/users/overview">Deutsch</a> |
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/fr/users/overview">français</a> |
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/ja/users/overview">日本語</a> |
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/ru/users/overview">Русский</a> |
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/pt-BR/users/overview">Português (Brasil)</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/de/">Deutsch</a> |
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/fr">français</a> |
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/ja/">日本語</a> |
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/ru">Русский</a> |
|
||||
<a href="https://qwenlm.github.io/qwen-code-docs/zh/">中文</a>
|
||||
|
||||
</div>
|
||||
Qwen Code is an open-source AI agent for the terminal, optimized for [Qwen3-Coder](https://github.com/QwenLM/Qwen3-Coder). It helps you understand large codebases, automate tedious work, and ship faster.
|
||||
|
||||
Qwen Code is a powerful command-line AI workflow tool adapted from [**Gemini CLI**](https://github.com/google-gemini/gemini-cli), specifically optimized for [Qwen3-Coder](https://github.com/QwenLM/Qwen3-Coder) models. It enhances your development workflow with advanced code understanding, automated tasks, and intelligent assistance.
|
||||

|
||||
|
||||
## 💡 Free Options Available
|
||||
## Why Qwen Code?
|
||||
|
||||
Get started with Qwen Code at no cost using any of these free options:
|
||||
|
||||
### 🔥 Qwen OAuth (Recommended)
|
||||
|
||||
- **2,000 requests per day** with no token limits
|
||||
- **60 requests per minute** rate limit
|
||||
- Simply run `qwen` and authenticate with your qwen.ai account
|
||||
- Automatic credential management and refresh
|
||||
- Use `/auth` command to switch to Qwen OAuth if you have initialized with OpenAI compatible mode
|
||||
|
||||
### 🌏 Regional Free Tiers
|
||||
|
||||
- **Mainland China**: ModelScope offers **2,000 free API calls per day**
|
||||
- **International**: OpenRouter provides **up to 1,000 free API calls per day** worldwide
|
||||
|
||||
For detailed setup instructions, see [Authorization](#authorization).
|
||||
|
||||
> [!WARNING]
|
||||
> **Token Usage Notice**: Qwen Code may issue multiple API calls per cycle, resulting in higher token usage (similar to Claude Code). We're actively optimizing API efficiency.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Code Understanding & Editing** - Query and edit large codebases beyond traditional context window limits
|
||||
- **Workflow Automation** - Automate operational tasks like handling pull requests and complex rebases
|
||||
- **Enhanced Parser** - Adapted parser specifically optimized for Qwen-Coder models
|
||||
- **Vision Model Support** - Automatically detect images in your input and seamlessly switch to vision-capable models for multimodal analysis
|
||||
- **OpenAI-compatible, OAuth free tier**: use an OpenAI-compatible API, or sign in with Qwen OAuth to get 2,000 free requests/day.
|
||||
- **Open-source, co-evolving**: both the framework and the Qwen3-Coder model are open-source—and they ship and evolve together.
|
||||
- **Agentic workflow, feature-rich**: rich built-in tools (Skills, SubAgents, Plan Mode) for a full agentic workflow and a Claude Code-like experience.
|
||||
- **Terminal-first, IDE-friendly**: built for developers who live in the command line, with optional integration for VS Code and Zed.
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ensure you have [Node.js version 20](https://nodejs.org/en/download) or higher installed.
|
||||
#### Prerequisites
|
||||
|
||||
```bash
|
||||
# Node.js 20+
|
||||
curl -qL https://www.npmjs.com/install.sh | sh
|
||||
```
|
||||
|
||||
### Install from npm
|
||||
#### NPM (recommended)
|
||||
|
||||
```bash
|
||||
npm install -g @qwen-code/qwen-code@latest
|
||||
qwen --version
|
||||
```
|
||||
|
||||
### Install from source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/QwenLM/qwen-code.git
|
||||
cd qwen-code
|
||||
npm install
|
||||
npm install -g .
|
||||
```
|
||||
|
||||
### Install globally with Homebrew (macOS/Linux)
|
||||
#### Homebrew (macOS, Linux)
|
||||
|
||||
```bash
|
||||
brew install qwen-code
|
||||
```
|
||||
|
||||
## VS Code Extension
|
||||
|
||||
In addition to the CLI tool, Qwen Code also provides a **VS Code extension** that brings AI-powered coding assistance directly into your editor with features like file system operations, native diffing, interactive chat, and more.
|
||||
|
||||
> 📦 The extension is currently in development. For installation, features, and development guide, see the [VS Code Extension README](./packages/vscode-ide-companion/README.md).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Start Qwen Code
|
||||
# Start Qwen Code (interactive)
|
||||
qwen
|
||||
|
||||
# Example commands
|
||||
> Explain this codebase structure
|
||||
> Help me refactor this function
|
||||
> Generate unit tests for this module
|
||||
# Then, in the session:
|
||||
/help
|
||||
/auth
|
||||
```
|
||||
|
||||
### Session Management
|
||||
On first use, you'll be prompted to sign in. You can run `/auth` anytime to switch authentication methods.
|
||||
|
||||
Control your token usage with configurable session limits to optimize costs and performance.
|
||||
Example prompts:
|
||||
|
||||
#### Configure Session Token Limit
|
||||
|
||||
Create or edit `.qwen/settings.json` in your home directory:
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionTokenLimit": 32000
|
||||
}
|
||||
```text
|
||||
What does this project do?
|
||||
Explain the codebase structure.
|
||||
Help me refactor this function.
|
||||
Generate unit tests for this module.
|
||||
```
|
||||
|
||||
#### Session Commands
|
||||
|
||||
- **`/compress`** - Compress conversation history to continue within token limits
|
||||
- **`/clear`** - Clear all conversation history and start fresh
|
||||
- **`/stats`** - Check current token usage and limits
|
||||
|
||||
> 📝 **Note**: Session token limit applies to a single conversation, not cumulative API calls.
|
||||
|
||||
### Vision Model Configuration
|
||||
|
||||
Qwen Code includes intelligent vision model auto-switching that detects images in your input and can automatically switch to vision-capable models for multimodal analysis. **This feature is enabled by default** - when you include images in your queries, you'll see a dialog asking how you'd like to handle the vision model switch.
|
||||
|
||||
#### Skip the Switch Dialog (Optional)
|
||||
|
||||
If you don't want to see the interactive dialog each time, configure the default behavior in your `.qwen/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"vlmSwitchMode": "once"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Available modes:**
|
||||
|
||||
- **`"once"`** - Switch to vision model for this query only, then revert
|
||||
- **`"session"`** - Switch to vision model for the entire session
|
||||
- **`"persist"`** - Continue with current model (no switching)
|
||||
- **Not set** - Show interactive dialog each time (default)
|
||||
|
||||
#### Command Line Override
|
||||
|
||||
You can also set the behavior via command line:
|
||||
|
||||
```bash
|
||||
# Switch once per query
|
||||
qwen --vlm-switch-mode once
|
||||
|
||||
# Switch for entire session
|
||||
qwen --vlm-switch-mode session
|
||||
|
||||
# Never switch automatically
|
||||
qwen --vlm-switch-mode persist
|
||||
```
|
||||
|
||||
#### Disable Vision Models (Optional)
|
||||
|
||||
To completely disable vision model support, add to your `.qwen/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"experimental": {
|
||||
"visionModelPreview": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 💡 **Tip**: In YOLO mode (`--yolo`), vision switching happens automatically without prompts when images are detected.
|
||||
|
||||
### Authorization
|
||||
|
||||
Choose your preferred authentication method based on your needs:
|
||||
|
||||
#### 1. Qwen OAuth (🚀 Recommended - Start in 30 seconds)
|
||||
|
||||
The easiest way to get started - completely free with generous quotas:
|
||||
|
||||
```bash
|
||||
# Just run this command and follow the browser authentication
|
||||
qwen
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
|
||||
1. **Instant Setup**: CLI opens your browser automatically
|
||||
2. **One-Click Login**: Authenticate with your qwen.ai account
|
||||
3. **Automatic Management**: Credentials cached locally for future use
|
||||
4. **No Configuration**: Zero setup required - just start coding!
|
||||
|
||||
**Free Tier Benefits:**
|
||||
|
||||
- ✅ **2,000 requests/day** (no token counting needed)
|
||||
- ✅ **60 requests/minute** rate limit
|
||||
- ✅ **Automatic credential refresh**
|
||||
- ✅ **Zero cost** for individual users
|
||||
- ℹ️ **Note**: Model fallback may occur to maintain service quality
|
||||
|
||||
#### 2. OpenAI-Compatible API
|
||||
|
||||
Use API keys for OpenAI or other compatible providers:
|
||||
|
||||
**Configuration Methods:**
|
||||
|
||||
1. **Environment Variables**
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="your_api_endpoint"
|
||||
export OPENAI_MODEL="your_model_choice"
|
||||
```
|
||||
|
||||
2. **Project `.env` File**
|
||||
Create a `.env` file in your project root:
|
||||
```env
|
||||
OPENAI_API_KEY=your_api_key_here
|
||||
OPENAI_BASE_URL=your_api_endpoint
|
||||
OPENAI_MODEL=your_model_choice
|
||||
```
|
||||
|
||||
**API Provider Options**
|
||||
|
||||
> ⚠️ **Regional Notice:**
|
||||
>
|
||||
> - **Mainland China**: Use Alibaba Cloud Bailian or ModelScope
|
||||
> - **International**: Use Alibaba Cloud ModelStudio or OpenRouter
|
||||
|
||||
<details>
|
||||
<summary><b>🇨🇳 For Users in Mainland China</b></summary>
|
||||
<summary>Click to watch a demo video</summary>
|
||||
|
||||
**Option 1: Alibaba Cloud Bailian** ([Apply for API Key](https://bailian.console.aliyun.com/))
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
export OPENAI_MODEL="qwen3-coder-plus"
|
||||
```
|
||||
|
||||
**Option 2: ModelScope (Free Tier)** ([Apply for API Key](https://modelscope.cn/docs/model-service/API-Inference/intro))
|
||||
|
||||
- ✅ **2,000 free API calls per day**
|
||||
- ⚠️ Connect your Aliyun account to avoid authentication errors
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="https://api-inference.modelscope.cn/v1"
|
||||
export OPENAI_MODEL="Qwen/Qwen3-Coder-480B-A35B-Instruct"
|
||||
```
|
||||
<video src="https://cloud.video.taobao.com/vod/HLfyppnCHplRV9Qhz2xSqeazHeRzYtG-EYJnHAqtzkQ.mp4" controls>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>🌍 For International Users</b></summary>
|
||||
## Authentication
|
||||
|
||||
**Option 1: Alibaba Cloud ModelStudio** ([Apply for API Key](https://modelstudio.console.alibabacloud.com/))
|
||||
Qwen Code supports two authentication methods:
|
||||
|
||||
- **Qwen OAuth (recommended & free)**: sign in with your `qwen.ai` account in a browser.
|
||||
- **OpenAI-compatible API**: use `OPENAI_API_KEY` (and optionally a custom base URL / model).
|
||||
|
||||
#### Qwen OAuth (recommended)
|
||||
|
||||
Start `qwen`, then run:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
||||
export OPENAI_MODEL="qwen3-coder-plus"
|
||||
/auth
|
||||
```
|
||||
|
||||
**Option 2: OpenRouter (Free Tier Available)** ([Apply for API Key](https://openrouter.ai/))
|
||||
Choose **Qwen OAuth** and complete the browser flow. Your credentials are cached locally so you usually won't need to log in again.
|
||||
|
||||
#### OpenAI-compatible API (API key)
|
||||
|
||||
Environment variables (recommended for CI / headless environments):
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export OPENAI_BASE_URL="https://openrouter.ai/api/v1"
|
||||
export OPENAI_MODEL="qwen/qwen3-coder:free"
|
||||
export OPENAI_API_KEY="your-api-key-here"
|
||||
export OPENAI_BASE_URL="https://api.openai.com/v1" # optional
|
||||
export OPENAI_MODEL="gpt-4o" # optional
|
||||
```
|
||||
|
||||
</details>
|
||||
For details (including `.qwen/.env` loading and security notes), see the [authentication guide](https://qwenlm.github.io/qwen-code-docs/en/users/configuration/auth/).
|
||||
|
||||
## Usage Examples
|
||||
## Usage
|
||||
|
||||
### 🔍 Explore Codebases
|
||||
As an open-source terminal agent, you can use Qwen Code in four primary ways:
|
||||
|
||||
1. Interactive mode (terminal UI)
|
||||
2. Headless mode (scripts, CI)
|
||||
3. IDE integration (VS Code, Zed)
|
||||
4. TypeScript SDK
|
||||
|
||||
#### Interactive mode
|
||||
|
||||
```bash
|
||||
cd your-project/
|
||||
qwen
|
||||
|
||||
# Architecture analysis
|
||||
> Describe the main pieces of this system's architecture
|
||||
> What are the key dependencies and how do they interact?
|
||||
> Find all API endpoints and their authentication methods
|
||||
```
|
||||
|
||||
### 💻 Code Development
|
||||
Run `qwen` in your project folder to launch the interactive terminal UI. Use `@` to reference local files (for example `@src/main.ts`).
|
||||
|
||||
#### Headless mode
|
||||
|
||||
```bash
|
||||
# Refactoring
|
||||
> Refactor this function to improve readability and performance
|
||||
> Convert this class to use dependency injection
|
||||
> Split this large module into smaller, focused components
|
||||
|
||||
# Code generation
|
||||
> Create a REST API endpoint for user management
|
||||
> Generate unit tests for the authentication module
|
||||
> Add error handling to all database operations
|
||||
cd your-project/
|
||||
qwen -p "your question"
|
||||
```
|
||||
|
||||
### 🔄 Automate Workflows
|
||||
Use `-p` to run Qwen Code without the interactive UI—ideal for scripts, automation, and CI/CD. Learn more: [Headless mode](https://qwenlm.github.io/qwen-code-docs/en/users/features/headless).
|
||||
|
||||
```bash
|
||||
# Git automation
|
||||
> Analyze git commits from the last 7 days, grouped by feature
|
||||
> Create a changelog from recent commits
|
||||
> Find all TODO comments and create GitHub issues
|
||||
#### IDE integration
|
||||
|
||||
# File operations
|
||||
> Convert all images in this directory to PNG format
|
||||
> Rename all test files to follow the *.test.ts pattern
|
||||
> Find and remove all console.log statements
|
||||
```
|
||||
Use Qwen Code inside your editor (VS Code and Zed):
|
||||
|
||||
### 🐛 Debugging & Analysis
|
||||
- [Use in VS Code](https://qwenlm.github.io/qwen-code-docs/en/users/integration-vscode/)
|
||||
- [Use in Zed](https://qwenlm.github.io/qwen-code-docs/en/users/integration-zed/)
|
||||
|
||||
```bash
|
||||
# Performance analysis
|
||||
> Identify performance bottlenecks in this React component
|
||||
> Find all N+1 query problems in the codebase
|
||||
#### TypeScript SDK
|
||||
|
||||
# Security audit
|
||||
> Check for potential SQL injection vulnerabilities
|
||||
> Find all hardcoded credentials or API keys
|
||||
```
|
||||
Build on top of Qwen Code with the TypeScript SDK:
|
||||
|
||||
## Popular Tasks
|
||||
|
||||
### 📚 Understand New Codebases
|
||||
|
||||
```text
|
||||
> What are the core business logic components?
|
||||
> What security mechanisms are in place?
|
||||
> How does the data flow through the system?
|
||||
> What are the main design patterns used?
|
||||
> Generate a dependency graph for this module
|
||||
```
|
||||
|
||||
### 🔨 Code Refactoring & Optimization
|
||||
|
||||
```text
|
||||
> What parts of this module can be optimized?
|
||||
> Help me refactor this class to follow SOLID principles
|
||||
> Add proper error handling and logging
|
||||
> Convert callbacks to async/await pattern
|
||||
> Implement caching for expensive operations
|
||||
```
|
||||
|
||||
### 📝 Documentation & Testing
|
||||
|
||||
```text
|
||||
> Generate comprehensive JSDoc comments for all public APIs
|
||||
> Write unit tests with edge cases for this component
|
||||
> Create API documentation in OpenAPI format
|
||||
> Add inline comments explaining complex algorithms
|
||||
> Generate a README for this module
|
||||
```
|
||||
|
||||
### 🚀 Development Acceleration
|
||||
|
||||
```text
|
||||
> Set up a new Express server with authentication
|
||||
> Create a React component with TypeScript and tests
|
||||
> Implement a rate limiter middleware
|
||||
> Add database migrations for new schema
|
||||
> Configure CI/CD pipeline for this project
|
||||
```
|
||||
- [Use the Qwen Code SDK](./packages/sdk-typescript/README.md)
|
||||
|
||||
## Commands & Shortcuts
|
||||
|
||||
@@ -386,6 +156,7 @@ qwen
|
||||
- `/clear` - Clear conversation history
|
||||
- `/compress` - Compress history to save tokens
|
||||
- `/stats` - Show current session information
|
||||
- `/bug` - Submit a bug report
|
||||
- `/exit` or `/quit` - Exit Qwen Code
|
||||
|
||||
### Keyboard Shortcuts
|
||||
@@ -394,6 +165,19 @@ qwen
|
||||
- `Ctrl+D` - Exit (on empty line)
|
||||
- `Up/Down` - Navigate command history
|
||||
|
||||
> Learn more about [Commands](https://qwenlm.github.io/qwen-code-docs/en/users/features/commands/)
|
||||
>
|
||||
> **Tip**: In YOLO mode (`--yolo`), vision switching happens automatically without prompts when images are detected. Learn more about [Approval Mode](https://qwenlm.github.io/qwen-code-docs/en/users/features/approval-mode/)
|
||||
|
||||
## Configuration
|
||||
|
||||
Qwen Code can be configured via `settings.json`, environment variables, and CLI flags.
|
||||
|
||||
- **User settings**: `~/.qwen/settings.json`
|
||||
- **Project settings**: `.qwen/settings.json`
|
||||
|
||||
See [settings](https://qwenlm.github.io/qwen-code-docs/en/users/configuration/settings/) for available options and precedence.
|
||||
|
||||
## Benchmark Results
|
||||
|
||||
### Terminal-Bench Performance
|
||||
@@ -403,24 +187,18 @@ qwen
|
||||
| Qwen Code | Qwen3-Coder-480A35 | 37.5% |
|
||||
| Qwen Code | Qwen3-Coder-30BA3B | 31.3% |
|
||||
|
||||
## Development & Contributing
|
||||
## Ecosystem
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md) to learn how to contribute to the project.
|
||||
Looking for a graphical interface?
|
||||
|
||||
For detailed authentication setup, see the [authentication guide](./docs/cli/authentication.md).
|
||||
- [**Gemini CLI Desktop**](https://github.com/Piebald-AI/gemini-cli-desktop) A cross-platform desktop/web/mobile UI for Qwen Code
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues, check the [troubleshooting guide](docs/troubleshooting.md).
|
||||
If you encounter issues, check the [troubleshooting guide](https://qwenlm.github.io/qwen-code-docs/en/users/support/troubleshooting/).
|
||||
|
||||
To report a bug from within the CLI, run `/bug` and include a short title and repro steps.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
This project is based on [Google Gemini CLI](https://github.com/google-gemini/gemini-cli). We acknowledge and appreciate the excellent work of the Gemini CLI team. Our main contribution focuses on parser-level adaptations to better support Qwen-Coder models.
|
||||
|
||||
## License
|
||||
|
||||
[LICENSE](./LICENSE)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#QwenLM/qwen-code&Date)
|
||||
|
||||
@@ -16,16 +16,15 @@ The plugin **MUST** run a local HTTP server that implements the **Model Context
|
||||
- **Endpoint:** The server should expose a single endpoint (e.g., `/mcp`) for all MCP communication.
|
||||
- **Port:** The server **MUST** listen on a dynamically assigned port (i.e., listen on port `0`).
|
||||
|
||||
### 2. Discovery Mechanism: The Port File
|
||||
### 2. Discovery Mechanism: The Lock File
|
||||
|
||||
For Qwen Code to connect, it needs to discover which IDE instance it's running in and what port your server is using. The plugin **MUST** facilitate this by creating a "discovery file."
|
||||
For Qwen Code to connect, it needs to discover what port your server is using. The plugin **MUST** facilitate this by creating a "lock file" and setting the port environment variable.
|
||||
|
||||
- **How the CLI Finds the File:** The CLI determines the Process ID (PID) of the IDE it's running in by traversing the process tree. It then looks for a discovery file that contains this PID in its name.
|
||||
- **File Location:** The file must be created in a specific directory: `os.tmpdir()/qwen/ide/`. Your plugin must create this directory if it doesn't exist.
|
||||
- **How the CLI Finds the File:** The CLI reads the port from `QWEN_CODE_IDE_SERVER_PORT`, then reads `~/.qwen/ide/<PORT>.lock`. (Legacy fallbacks exist for older extensions; see note below.)
|
||||
- **File Location:** The file must be created in a specific directory: `~/.qwen/ide/`. Your plugin must create this directory if it doesn't exist.
|
||||
- **File Naming Convention:** The filename is critical and **MUST** follow the pattern:
|
||||
`qwen-code-ide-server-${PID}-${PORT}.json`
|
||||
- `${PID}`: The process ID of the parent IDE process. Your plugin must determine this PID and include it in the filename.
|
||||
- `${PORT}`: The port your MCP server is listening on.
|
||||
`<PORT>.lock`
|
||||
- `<PORT>`: The port your MCP server is listening on.
|
||||
- **File Content & Workspace Validation:** The file **MUST** contain a JSON object with the following structure:
|
||||
|
||||
```json
|
||||
@@ -33,21 +32,20 @@ For Qwen Code to connect, it needs to discover which IDE instance it's running i
|
||||
"port": 12345,
|
||||
"workspacePath": "/path/to/project1:/path/to/project2",
|
||||
"authToken": "a-very-secret-token",
|
||||
"ideInfo": {
|
||||
"name": "vscode",
|
||||
"displayName": "VS Code"
|
||||
}
|
||||
"ppid": 1234,
|
||||
"ideName": "VS Code"
|
||||
}
|
||||
```
|
||||
- `port` (number, required): The port of the MCP server.
|
||||
- `workspacePath` (string, required): A list of all open workspace root paths, delimited by the OS-specific path separator (`:` for Linux/macOS, `;` for Windows). The CLI uses this path to ensure it's running in the same project folder that's open in the IDE. If the CLI's current working directory is not a sub-directory of `workspacePath`, the connection will be rejected. Your plugin **MUST** provide the correct, absolute path(s) to the root of the open workspace(s).
|
||||
- `authToken` (string, required): A secret token for securing the connection. The CLI will include this token in an `Authorization: Bearer <token>` header on all requests.
|
||||
- `ideInfo` (object, required): Information about the IDE.
|
||||
- `name` (string, required): A short, lowercase identifier for the IDE (e.g., `vscode`, `jetbrains`).
|
||||
- `displayName` (string, required): A user-friendly name for the IDE (e.g., `VS Code`, `JetBrains IDE`).
|
||||
- `ppid` (number, required): The parent process ID of the IDE process.
|
||||
- `ideName` (string, required): A user-friendly name for the IDE (e.g., `VS Code`, `JetBrains IDE`).
|
||||
|
||||
- **Authentication:** To secure the connection, the plugin **MUST** generate a unique, secret token and include it in the discovery file. The CLI will then include this token in the `Authorization` header for all requests to the MCP server (e.g., `Authorization: Bearer a-very-secret-token`). Your server **MUST** validate this token on every request and reject any that are unauthorized.
|
||||
- **Tie-Breaking with Environment Variables (Recommended):** For the most reliable experience, your plugin **SHOULD** both create the discovery file and set the `QWEN_CODE_IDE_SERVER_PORT` environment variable in the integrated terminal. The file serves as the primary discovery mechanism, but the environment variable is crucial for tie-breaking. If a user has multiple IDE windows open for the same workspace, the CLI uses the `QWEN_CODE_IDE_SERVER_PORT` variable to identify and connect to the correct window's server.
|
||||
- **Environment Variables (Required):** Your plugin **MUST** set `QWEN_CODE_IDE_SERVER_PORT` in the integrated terminal so the CLI can locate the correct `<PORT>.lock` file.
|
||||
|
||||
**Legacy note:** For extensions older than v0.5.1, Qwen Code may fall back to reading JSON files in the system temp directory named `qwen-code-ide-server-<PID>.json` or `qwen-code-ide-server-<PORT>.json`. New integrations should not rely on these legacy files.
|
||||
|
||||
## II. The Context Interface
|
||||
|
||||
|
||||
60
package-lock.json
generated
60
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -568,7 +568,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -592,7 +591,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -2157,7 +2155,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -3671,7 +3668,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
@@ -4142,7 +4138,6 @@
|
||||
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -4153,7 +4148,6 @@
|
||||
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
@@ -4359,7 +4353,6 @@
|
||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
@@ -5135,7 +5128,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5530,7 +5522,8 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/array-includes": {
|
||||
"version": "3.1.9",
|
||||
@@ -6865,6 +6858,7 @@
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
@@ -7982,7 +7976,6 @@
|
||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -8518,6 +8511,7 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
@@ -8579,6 +8573,7 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -8588,6 +8583,7 @@
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
@@ -8597,6 +8593,7 @@
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
@@ -8763,6 +8760,7 @@
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~2.0.0",
|
||||
@@ -8781,6 +8779,7 @@
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
@@ -8789,13 +8788,15 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/finalhandler/node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
@@ -9909,7 +9910,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ink/-/ink-6.2.3.tgz",
|
||||
"integrity": "sha512-fQkfEJjKbLXIcVWEE3MvpYSnwtbbmRsmeNDNz1pIuOFlwE+UF2gsy228J36OXKZGWJWZJKUigphBSqCNMcARtg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alcalzone/ansi-tokenize": "^0.2.0",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
@@ -11864,6 +11864,7 @@
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -13162,7 +13163,8 @@
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "3.0.0",
|
||||
@@ -13821,7 +13823,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -13832,7 +13833,6 @@
|
||||
"integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"shell-quote": "^1.6.1",
|
||||
"ws": "^7"
|
||||
@@ -13866,7 +13866,6 @@
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@@ -15932,7 +15931,6 @@
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -16112,8 +16110,7 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.20.3",
|
||||
@@ -16121,7 +16118,6 @@
|
||||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
@@ -16316,7 +16312,6 @@
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -16624,6 +16619,7 @@
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
@@ -16679,7 +16675,6 @@
|
||||
"integrity": "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
@@ -16793,7 +16788,6 @@
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -16807,7 +16801,6 @@
|
||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
@@ -17486,7 +17479,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
@@ -17502,7 +17494,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"dependencies": {
|
||||
"@google/genai": "1.16.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
@@ -17626,7 +17618,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@qwen-code/qwen-code-core",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@google/genai": "1.16.0",
|
||||
@@ -17757,7 +17749,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -17776,10 +17767,11 @@
|
||||
},
|
||||
"packages/sdk-typescript": {
|
||||
"name": "@qwen-code/sdk",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.4"
|
||||
"@modelcontextprotocol/sdk": "^1.0.4",
|
||||
"tiktoken": "^1.0.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.0",
|
||||
@@ -20205,7 +20197,7 @@
|
||||
},
|
||||
"packages/test-utils": {
|
||||
"name": "@qwen-code/qwen-code-test-utils",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
@@ -20217,7 +20209,7 @@
|
||||
},
|
||||
"packages/vscode-ide-companion": {
|
||||
"name": "qwen-code-vscode-ide-companion",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"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.5.1"
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env node scripts/start.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"description": "Qwen Code",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -33,7 +33,7 @@
|
||||
"dist"
|
||||
],
|
||||
"config": {
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.5.1"
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "1.16.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code-core",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"description": "Qwen Code Core",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -15,6 +15,7 @@ export const OAUTH_FILE = 'oauth_creds.json';
|
||||
const TMP_DIR_NAME = 'tmp';
|
||||
const BIN_DIR_NAME = 'bin';
|
||||
const PROJECT_DIR_NAME = 'projects';
|
||||
const IDE_DIR_NAME = 'ide';
|
||||
|
||||
export class Storage {
|
||||
private readonly targetDir: string;
|
||||
@@ -59,6 +60,10 @@ export class Storage {
|
||||
return path.join(Storage.getGlobalQwenDir(), TMP_DIR_NAME);
|
||||
}
|
||||
|
||||
static getGlobalIdeDir(): string {
|
||||
return path.join(Storage.getGlobalQwenDir(), IDE_DIR_NAME);
|
||||
}
|
||||
|
||||
static getGlobalBinDir(): string {
|
||||
return path.join(Storage.getGlobalQwenDir(), BIN_DIR_NAME);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ vi.mock('node:fs', async (importOriginal) => {
|
||||
...actual.promises,
|
||||
readFile: vi.fn(),
|
||||
readdir: vi.fn(),
|
||||
stat: vi.fn(),
|
||||
},
|
||||
realpathSync: (p: string) => p,
|
||||
existsSync: () => false,
|
||||
@@ -68,6 +69,7 @@ describe('IdeClient', () => {
|
||||
command: 'test-ide',
|
||||
});
|
||||
vi.mocked(os.tmpdir).mockReturnValue('/tmp');
|
||||
vi.mocked(os.homedir).mockReturnValue('/home/test');
|
||||
|
||||
// Mock MCP client and transports
|
||||
mockClient = {
|
||||
@@ -97,19 +99,15 @@ describe('IdeClient', () => {
|
||||
|
||||
describe('connect', () => {
|
||||
it('should connect using HTTP when port is provided in config file', async () => {
|
||||
process.env['QWEN_CODE_IDE_SERVER_PORT'] = '8080';
|
||||
const config = { port: '8080' };
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config));
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([]);
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.connect();
|
||||
|
||||
expect(fs.promises.readFile).toHaveBeenCalledWith(
|
||||
path.join('/tmp', 'qwen-code-ide-server-12345.json'),
|
||||
path.join('/home/test', '.qwen', 'ide', '8080.lock'),
|
||||
'utf8',
|
||||
);
|
||||
expect(StreamableHTTPClientTransport).toHaveBeenCalledWith(
|
||||
@@ -120,16 +118,13 @@ describe('IdeClient', () => {
|
||||
expect(ideClient.getConnectionStatus().status).toBe(
|
||||
IDEConnectionStatus.Connected,
|
||||
);
|
||||
delete process.env['QWEN_CODE_IDE_SERVER_PORT'];
|
||||
});
|
||||
|
||||
it('should connect using stdio when stdio config is provided in file', async () => {
|
||||
process.env['QWEN_CODE_IDE_SERVER_PORT'] = '8080';
|
||||
const config = { stdio: { command: 'test-cmd', args: ['--foo'] } };
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config));
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([]);
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.connect();
|
||||
@@ -142,19 +137,16 @@ describe('IdeClient', () => {
|
||||
expect(ideClient.getConnectionStatus().status).toBe(
|
||||
IDEConnectionStatus.Connected,
|
||||
);
|
||||
delete process.env['QWEN_CODE_IDE_SERVER_PORT'];
|
||||
});
|
||||
|
||||
it('should prioritize port over stdio when both are in config file', async () => {
|
||||
process.env['QWEN_CODE_IDE_SERVER_PORT'] = '8080';
|
||||
const config = {
|
||||
port: '8080',
|
||||
stdio: { command: 'test-cmd', args: ['--foo'] },
|
||||
};
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config));
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([]);
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
await ideClient.connect();
|
||||
@@ -164,6 +156,7 @@ describe('IdeClient', () => {
|
||||
expect(ideClient.getConnectionStatus().status).toBe(
|
||||
IDEConnectionStatus.Connected,
|
||||
);
|
||||
delete process.env['QWEN_CODE_IDE_SERVER_PORT'];
|
||||
});
|
||||
|
||||
it('should connect using HTTP when port is provided in environment variables', async () => {
|
||||
@@ -263,7 +256,8 @@ describe('IdeClient', () => {
|
||||
});
|
||||
|
||||
describe('getConnectionConfigFromFile', () => {
|
||||
it('should return config from the specific pid file if it exists', async () => {
|
||||
it('should return config from the env port lock file if it exists', async () => {
|
||||
process.env['QWEN_CODE_IDE_SERVER_PORT'] = '1234';
|
||||
const config = { port: '1234', workspacePath: '/test/workspace' };
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config));
|
||||
|
||||
@@ -277,18 +271,14 @@ describe('IdeClient', () => {
|
||||
|
||||
expect(result).toEqual(config);
|
||||
expect(fs.promises.readFile).toHaveBeenCalledWith(
|
||||
path.join('/tmp', 'qwen-code-ide-server-12345.json'),
|
||||
path.join('/home/test', '.qwen', 'ide', '1234.lock'),
|
||||
'utf8',
|
||||
);
|
||||
delete process.env['QWEN_CODE_IDE_SERVER_PORT'];
|
||||
});
|
||||
|
||||
it('should return undefined if no config files are found', async () => {
|
||||
vi.mocked(fs.promises.readFile).mockRejectedValue(new Error('not found'));
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([]);
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const result = await (
|
||||
@@ -300,20 +290,15 @@ describe('IdeClient', () => {
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should find and parse a single config file with the new naming scheme', async () => {
|
||||
const config = { port: '5678', workspacePath: '/test/workspace' };
|
||||
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
||||
new Error('not found'),
|
||||
); // For old path
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue(['qwen-code-ide-server-12345-123.json']);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config));
|
||||
vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({
|
||||
isValid: true,
|
||||
});
|
||||
it('should read legacy pid config when available', async () => {
|
||||
const config = {
|
||||
port: '5678',
|
||||
workspacePath: '/test/workspace',
|
||||
ppid: 12345,
|
||||
};
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValueOnce(
|
||||
JSON.stringify(config),
|
||||
);
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const result = await (
|
||||
@@ -324,110 +309,18 @@ describe('IdeClient', () => {
|
||||
|
||||
expect(result).toEqual(config);
|
||||
expect(fs.promises.readFile).toHaveBeenCalledWith(
|
||||
path.join('/tmp/gemini/ide', 'qwen-code-ide-server-12345-123.json'),
|
||||
path.join('/tmp', 'qwen-code-ide-server-12345.json'),
|
||||
'utf8',
|
||||
);
|
||||
});
|
||||
|
||||
it('should filter out configs with invalid workspace paths', async () => {
|
||||
const validConfig = {
|
||||
port: '5678',
|
||||
workspacePath: '/test/workspace',
|
||||
};
|
||||
const invalidConfig = {
|
||||
port: '1111',
|
||||
workspacePath: '/invalid/workspace',
|
||||
};
|
||||
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
||||
new Error('not found'),
|
||||
);
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([
|
||||
'qwen-code-ide-server-12345-111.json',
|
||||
'qwen-code-ide-server-12345-222.json',
|
||||
]);
|
||||
vi.mocked(fs.promises.readFile)
|
||||
.mockResolvedValueOnce(JSON.stringify(invalidConfig))
|
||||
.mockResolvedValueOnce(JSON.stringify(validConfig));
|
||||
|
||||
const validateSpy = vi
|
||||
.spyOn(IdeClient, 'validateWorkspacePath')
|
||||
.mockReturnValueOnce({ isValid: false })
|
||||
.mockReturnValueOnce({ isValid: true });
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const result = await (
|
||||
ideClient as unknown as {
|
||||
getConnectionConfigFromFile: () => Promise<unknown>;
|
||||
}
|
||||
).getConnectionConfigFromFile();
|
||||
|
||||
expect(result).toEqual(validConfig);
|
||||
expect(validateSpy).toHaveBeenCalledWith(
|
||||
'/invalid/workspace',
|
||||
'/test/workspace/sub-dir',
|
||||
);
|
||||
expect(validateSpy).toHaveBeenCalledWith(
|
||||
'/test/workspace',
|
||||
'/test/workspace/sub-dir',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the first valid config when multiple workspaces are valid', async () => {
|
||||
const config1 = { port: '1111', workspacePath: '/test/workspace' };
|
||||
const config2 = { port: '2222', workspacePath: '/test/workspace2' };
|
||||
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
||||
new Error('not found'),
|
||||
);
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([
|
||||
'qwen-code-ide-server-12345-111.json',
|
||||
'qwen-code-ide-server-12345-222.json',
|
||||
]);
|
||||
vi.mocked(fs.promises.readFile)
|
||||
.mockResolvedValueOnce(JSON.stringify(config1))
|
||||
.mockResolvedValueOnce(JSON.stringify(config2));
|
||||
vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({
|
||||
isValid: true,
|
||||
});
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const result = await (
|
||||
ideClient as unknown as {
|
||||
getConnectionConfigFromFile: () => Promise<unknown>;
|
||||
}
|
||||
).getConnectionConfigFromFile();
|
||||
|
||||
expect(result).toEqual(config1);
|
||||
});
|
||||
|
||||
it('should prioritize the config matching the port from the environment variable', async () => {
|
||||
it('should fall back to legacy port file when pid file is missing', async () => {
|
||||
process.env['QWEN_CODE_IDE_SERVER_PORT'] = '2222';
|
||||
const config1 = { port: '1111', workspacePath: '/test/workspace' };
|
||||
const config2 = { port: '2222', workspacePath: '/test/workspace2' };
|
||||
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
||||
new Error('not found'),
|
||||
);
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([
|
||||
'qwen-code-ide-server-12345-111.json',
|
||||
'qwen-code-ide-server-12345-222.json',
|
||||
]);
|
||||
vi.mocked(fs.promises.readFile)
|
||||
.mockResolvedValueOnce(JSON.stringify(config1))
|
||||
.mockRejectedValueOnce(new Error('not found')) // ~/.qwen/ide/<port>.lock
|
||||
.mockRejectedValueOnce(new Error('not found')) // legacy pid file
|
||||
.mockResolvedValueOnce(JSON.stringify(config2));
|
||||
vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({
|
||||
isValid: true,
|
||||
});
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const result = await (
|
||||
@@ -437,28 +330,23 @@ describe('IdeClient', () => {
|
||||
).getConnectionConfigFromFile();
|
||||
|
||||
expect(result).toEqual(config2);
|
||||
expect(fs.promises.readFile).toHaveBeenCalledWith(
|
||||
path.join('/tmp', 'qwen-code-ide-server-12345.json'),
|
||||
'utf8',
|
||||
);
|
||||
expect(fs.promises.readFile).toHaveBeenCalledWith(
|
||||
path.join('/tmp', 'qwen-code-ide-server-2222.json'),
|
||||
'utf8',
|
||||
);
|
||||
delete process.env['QWEN_CODE_IDE_SERVER_PORT'];
|
||||
});
|
||||
|
||||
it('should handle invalid JSON in one of the config files', async () => {
|
||||
const validConfig = { port: '2222', workspacePath: '/test/workspace' };
|
||||
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
||||
new Error('not found'),
|
||||
);
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([
|
||||
'qwen-code-ide-server-12345-111.json',
|
||||
'qwen-code-ide-server-12345-222.json',
|
||||
]);
|
||||
it('should fall back to legacy config when env lock file has invalid JSON', async () => {
|
||||
process.env['QWEN_CODE_IDE_SERVER_PORT'] = '3333';
|
||||
const config = { port: '1111', workspacePath: '/test/workspace' };
|
||||
vi.mocked(fs.promises.readFile)
|
||||
.mockResolvedValueOnce('invalid json')
|
||||
.mockResolvedValueOnce(JSON.stringify(validConfig));
|
||||
vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({
|
||||
isValid: true,
|
||||
});
|
||||
.mockResolvedValueOnce(JSON.stringify(config));
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const result = await (
|
||||
@@ -467,96 +355,7 @@ describe('IdeClient', () => {
|
||||
}
|
||||
).getConnectionConfigFromFile();
|
||||
|
||||
expect(result).toEqual(validConfig);
|
||||
});
|
||||
|
||||
it('should return undefined if readdir throws an error', async () => {
|
||||
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
||||
new Error('not found'),
|
||||
);
|
||||
vi.mocked(fs.promises.readdir).mockRejectedValue(
|
||||
new Error('readdir failed'),
|
||||
);
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const result = await (
|
||||
ideClient as unknown as {
|
||||
getConnectionConfigFromFile: () => Promise<unknown>;
|
||||
}
|
||||
).getConnectionConfigFromFile();
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should ignore files with invalid names', async () => {
|
||||
const validConfig = { port: '3333', workspacePath: '/test/workspace' };
|
||||
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
||||
new Error('not found'),
|
||||
);
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([
|
||||
'qwen-code-ide-server-12345-111.json', // valid
|
||||
'not-a-config-file.txt', // invalid
|
||||
'qwen-code-ide-server-asdf.json', // invalid
|
||||
]);
|
||||
vi.mocked(fs.promises.readFile).mockResolvedValueOnce(
|
||||
JSON.stringify(validConfig),
|
||||
);
|
||||
vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({
|
||||
isValid: true,
|
||||
});
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const result = await (
|
||||
ideClient as unknown as {
|
||||
getConnectionConfigFromFile: () => Promise<unknown>;
|
||||
}
|
||||
).getConnectionConfigFromFile();
|
||||
|
||||
expect(result).toEqual(validConfig);
|
||||
expect(fs.promises.readFile).toHaveBeenCalledWith(
|
||||
path.join('/tmp/gemini/ide', 'qwen-code-ide-server-12345-111.json'),
|
||||
'utf8',
|
||||
);
|
||||
expect(fs.promises.readFile).not.toHaveBeenCalledWith(
|
||||
path.join('/tmp/gemini/ide', 'not-a-config-file.txt'),
|
||||
'utf8',
|
||||
);
|
||||
});
|
||||
|
||||
it('should match env port string to a number port in the config', async () => {
|
||||
process.env['QWEN_CODE_IDE_SERVER_PORT'] = '3333';
|
||||
const config1 = { port: 1111, workspacePath: '/test/workspace' };
|
||||
const config2 = { port: 3333, workspacePath: '/test/workspace2' };
|
||||
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
|
||||
new Error('not found'),
|
||||
);
|
||||
(
|
||||
vi.mocked(fs.promises.readdir) as Mock<
|
||||
(path: fs.PathLike) => Promise<string[]>
|
||||
>
|
||||
).mockResolvedValue([
|
||||
'qwen-code-ide-server-12345-111.json',
|
||||
'qwen-code-ide-server-12345-222.json',
|
||||
]);
|
||||
vi.mocked(fs.promises.readFile)
|
||||
.mockResolvedValueOnce(JSON.stringify(config1))
|
||||
.mockResolvedValueOnce(JSON.stringify(config2));
|
||||
vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({
|
||||
isValid: true,
|
||||
});
|
||||
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
const result = await (
|
||||
ideClient as unknown as {
|
||||
getConnectionConfigFromFile: () => Promise<unknown>;
|
||||
}
|
||||
).getConnectionConfigFromFile();
|
||||
|
||||
expect(result).toEqual(config2);
|
||||
expect(result).toEqual(config);
|
||||
delete process.env['QWEN_CODE_IDE_SERVER_PORT'];
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as fs from 'node:fs';
|
||||
import { isSubpath } from '../utils/paths.js';
|
||||
import { detectIde, type IdeInfo } from '../ide/detect-ide.js';
|
||||
import { ideContextStore } from './ideContext.js';
|
||||
import { Storage } from '../config/storage.js';
|
||||
import {
|
||||
IdeContextNotificationSchema,
|
||||
IdeDiffAcceptedNotificationSchema,
|
||||
@@ -572,98 +573,103 @@ export class IdeClient {
|
||||
| (ConnectionConfig & { workspacePath?: string; ideInfo?: IdeInfo })
|
||||
| undefined
|
||||
> {
|
||||
if (!this.ideProcessInfo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// For backwards compatability
|
||||
try {
|
||||
const portFile = path.join(
|
||||
os.tmpdir(),
|
||||
`qwen-code-ide-server-${this.ideProcessInfo.pid}.json`,
|
||||
);
|
||||
const portFileContents = await fs.promises.readFile(portFile, 'utf8');
|
||||
return JSON.parse(portFileContents);
|
||||
} catch (_) {
|
||||
// For newer extension versions, the file name matches the pattern
|
||||
// /^qwen-code-ide-server-${pid}-\d+\.json$/. If multiple IDE
|
||||
// windows are open, multiple files matching the pattern are expected to
|
||||
// exist.
|
||||
}
|
||||
|
||||
const portFileDir = path.join(os.tmpdir(), 'gemini', 'ide');
|
||||
let portFiles;
|
||||
try {
|
||||
portFiles = await fs.promises.readdir(portFileDir);
|
||||
} catch (e) {
|
||||
logger.debug('Failed to read IDE connection directory:', e);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!portFiles) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fileRegex = new RegExp(
|
||||
`^qwen-code-ide-server-${this.ideProcessInfo.pid}-\\d+\\.json$`,
|
||||
);
|
||||
const matchingFiles = portFiles
|
||||
.filter((file) => fileRegex.test(file))
|
||||
.sort();
|
||||
if (matchingFiles.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let fileContents: string[];
|
||||
try {
|
||||
fileContents = await Promise.all(
|
||||
matchingFiles.map((file) =>
|
||||
fs.promises.readFile(path.join(portFileDir, file), 'utf8'),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
logger.debug('Failed to read IDE connection config file(s):', e);
|
||||
return undefined;
|
||||
}
|
||||
const parsedContents = fileContents.map((content) => {
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
} catch (e) {
|
||||
logger.debug('Failed to parse JSON from config file: ', e);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
const validWorkspaces = parsedContents.filter((content) => {
|
||||
if (!content) {
|
||||
return false;
|
||||
}
|
||||
const { isValid } = IdeClient.validateWorkspacePath(
|
||||
content.workspacePath,
|
||||
process.cwd(),
|
||||
);
|
||||
return isValid;
|
||||
});
|
||||
|
||||
if (validWorkspaces.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (validWorkspaces.length === 1) {
|
||||
return validWorkspaces[0];
|
||||
}
|
||||
|
||||
const portFromEnv = this.getPortFromEnv();
|
||||
if (portFromEnv) {
|
||||
const matchingPort = validWorkspaces.find(
|
||||
(content) => String(content.port) === portFromEnv,
|
||||
);
|
||||
if (matchingPort) {
|
||||
return matchingPort;
|
||||
try {
|
||||
const ideDir = Storage.getGlobalIdeDir();
|
||||
const lockFile = path.join(ideDir, `${portFromEnv}.lock`);
|
||||
const lockFileContents = await fs.promises.readFile(lockFile, 'utf8');
|
||||
return JSON.parse(lockFileContents);
|
||||
} catch (_) {
|
||||
// Fall through to legacy discovery.
|
||||
}
|
||||
}
|
||||
|
||||
return validWorkspaces[0];
|
||||
// Legacy discovery for VSCode extension < v0.5.1.
|
||||
return this.getLegacyConnectionConfig(portFromEnv);
|
||||
}
|
||||
|
||||
// Legacy connection files were written in the global temp directory.
|
||||
private async getLegacyConnectionConfig(
|
||||
portFromEnv?: string,
|
||||
): Promise<
|
||||
| (ConnectionConfig & { workspacePath?: string; ideInfo?: IdeInfo })
|
||||
| undefined
|
||||
> {
|
||||
if (this.ideProcessInfo) {
|
||||
try {
|
||||
const portFile = path.join(
|
||||
os.tmpdir(),
|
||||
`qwen-code-ide-server-${this.ideProcessInfo.pid}.json`,
|
||||
);
|
||||
const portFileContents = await fs.promises.readFile(portFile, 'utf8');
|
||||
return JSON.parse(portFileContents);
|
||||
} catch (_) {
|
||||
// For older/newer extension versions, the file name matches the pattern
|
||||
// /^qwen-code-ide-server-${pid}-\d+\.json$/. If multiple IDE
|
||||
// windows are open, multiple files matching the pattern are expected to
|
||||
// exist.
|
||||
}
|
||||
}
|
||||
|
||||
if (portFromEnv) {
|
||||
try {
|
||||
const portFile = path.join(
|
||||
os.tmpdir(),
|
||||
`qwen-code-ide-server-${portFromEnv}.json`,
|
||||
);
|
||||
const portFileContents = await fs.promises.readFile(portFile, 'utf8');
|
||||
return JSON.parse(portFileContents);
|
||||
} catch (_) {
|
||||
// Ignore and fall through.
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected async getAllConnectionConfigs(
|
||||
ideDir: string,
|
||||
): Promise<
|
||||
ConnectionConfig & Array<{ workspacePath?: string; ideInfo?: IdeInfo }>
|
||||
> {
|
||||
const fileRegex = new RegExp('^\\d+\\.lock$');
|
||||
let lockFiles: string[];
|
||||
try {
|
||||
lockFiles = (await fs.promises.readdir(ideDir)).filter((file) =>
|
||||
fileRegex.test(file),
|
||||
);
|
||||
} catch (e) {
|
||||
logger.debug('Failed to read IDE connection directory:', e);
|
||||
return [];
|
||||
}
|
||||
|
||||
const fileContents = await Promise.all(
|
||||
lockFiles.map(async (file) => {
|
||||
const fullPath = path.join(ideDir, file);
|
||||
try {
|
||||
const stat = await fs.promises.stat(fullPath);
|
||||
const content = await fs.promises.readFile(fullPath, 'utf8');
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
return { file, mtimeMs: stat.mtimeMs, parsed };
|
||||
} catch (e) {
|
||||
logger.debug('Failed to parse JSON from lock file: ', e);
|
||||
return { file, mtimeMs: stat.mtimeMs, parsed: undefined };
|
||||
}
|
||||
} catch (e) {
|
||||
// If we can't stat/read the file, treat it as very old so it doesn't
|
||||
// win ties, and skip parsing by returning undefined content.
|
||||
logger.debug('Failed to read/stat IDE lock file:', e);
|
||||
return { file, mtimeMs: -Infinity, parsed: undefined };
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return fileContents
|
||||
.filter(({ parsed }) => parsed !== undefined)
|
||||
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
||||
.map(({ parsed }) => parsed);
|
||||
}
|
||||
|
||||
private createProxyAwareFetch() {
|
||||
|
||||
@@ -20,11 +20,26 @@ async function getProcessInfo(pid: number): Promise<{
|
||||
command: string;
|
||||
}> {
|
||||
// Only used for Unix systems (macOS and Linux)
|
||||
const { stdout } = await execAsync(`ps -p ${pid} -o ppid=,comm=`);
|
||||
const [ppidStr, ...commandParts] = stdout.trim().split(/\s+/);
|
||||
const parentPid = parseInt(ppidStr, 10);
|
||||
const command = commandParts.join(' ');
|
||||
return { parentPid, name: path.basename(command), command };
|
||||
try {
|
||||
const command = `ps -o ppid=,command= -p ${pid}`;
|
||||
const { stdout } = await execAsync(command);
|
||||
const trimmedStdout = stdout.trim();
|
||||
if (!trimmedStdout) {
|
||||
return { parentPid: 0, name: '', command: '' };
|
||||
}
|
||||
const parts = trimmedStdout.split(/\s+/);
|
||||
const ppidString = parts[0];
|
||||
const parentPid = parseInt(ppidString, 10);
|
||||
const fullCommand = trimmedStdout.substring(ppidString.length).trim();
|
||||
const processName = path.basename(fullCommand.split(' ')[0]);
|
||||
return {
|
||||
parentPid: isNaN(parentPid) ? 1 : parentPid,
|
||||
name: processName,
|
||||
command: fullCommand,
|
||||
};
|
||||
} catch (_e) {
|
||||
return { parentPid: 0, name: '', command: '' };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Finds the IDE process info on Unix-like systems.
|
||||
|
||||
24
packages/sdk-java/.editorconfig
Normal file
24
packages/sdk-java/.editorconfig
Normal file
@@ -0,0 +1,24 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
ij_continuation_indent_size = 8
|
||||
|
||||
[*.java]
|
||||
ij_java_doc_align_exception_comments = false
|
||||
ij_java_doc_align_param_comments = false
|
||||
|
||||
[*.{yaml, yml, sh, ps1}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{md, mkd, markdown}]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{**/res/**.xml, **/AndroidManifest.xml}]
|
||||
ij_continuation_indent_size = 4
|
||||
14
packages/sdk-java/.gitignore
vendored
Normal file
14
packages/sdk-java/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
||||
# Maven
|
||||
log/
|
||||
target/
|
||||
|
||||
/docs/
|
||||
378
packages/sdk-java/QWEN.md
Normal file
378
packages/sdk-java/QWEN.md
Normal file
@@ -0,0 +1,378 @@
|
||||
# Qwen Code Java SDK
|
||||
|
||||
## Project Overview
|
||||
|
||||
The Qwen Code Java SDK is a minimum experimental SDK for programmatic access to Qwen Code functionality. It provides a Java interface to interact with the Qwen Code CLI, allowing developers to integrate Qwen Code capabilities into their Java applications.
|
||||
|
||||
**Context Information:**
|
||||
|
||||
- Current Date: Monday 5 January 2026
|
||||
- Operating System: darwin
|
||||
- Working Directory: /Users/weigeng/repos/qwen-code/packages/sdk-java
|
||||
|
||||
## Project Details
|
||||
|
||||
- **Group ID**: com.alibaba
|
||||
- **Artifact ID**: qwencode-sdk (as per pom.xml)
|
||||
- **Version**: 0.0.1-SNAPSHOT
|
||||
- **Packaging**: JAR
|
||||
- **Java Version**: 1.8+ (source and target)
|
||||
- **License**: Apache-2.0
|
||||
|
||||
## Architecture
|
||||
|
||||
The SDK follows a layered architecture:
|
||||
|
||||
- **API Layer**: Provides the main entry points through `QwenCodeCli` class with simple static methods for basic usage
|
||||
- **Session Layer**: Manages communication sessions with the Qwen Code CLI through the `Session` class
|
||||
- **Transport Layer**: Handles the communication mechanism between the SDK and CLI process (currently using process transport via `ProcessTransport`)
|
||||
- **Protocol Layer**: Defines data structures for communication based on the CLI protocol
|
||||
- **Utils**: Common utilities for concurrent execution, timeout handling, and error management
|
||||
|
||||
## Key Components
|
||||
|
||||
### Main Classes
|
||||
|
||||
- `QwenCodeCli`: Main entry point with static methods for simple queries
|
||||
- `Session`: Manages communication sessions with the CLI
|
||||
- `Transport`: Abstracts the communication mechanism (currently using process transport)
|
||||
- `ProcessTransport`: Implementation that communicates via process execution
|
||||
- `TransportOptions`: Configuration class for transport layer settings
|
||||
- `SessionEventSimpleConsumers`: High-level event handler for processing responses
|
||||
- `AssistantContentSimpleConsumers`: Handles different types of content within assistant messages
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **Logging**: ch.qos.logback:logback-classic
|
||||
- **Utilities**: org.apache.commons:commons-lang3
|
||||
- **JSON Processing**: com.alibaba.fastjson2:fastjson2
|
||||
- **Testing**: JUnit 5 (org.junit.jupiter:junit-jupiter)
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Java 8 or higher
|
||||
- Apache Maven 3.6.0 or higher
|
||||
|
||||
### Build Commands
|
||||
|
||||
```bash
|
||||
# Compile the project
|
||||
mvn compile
|
||||
|
||||
# Run tests
|
||||
mvn test
|
||||
|
||||
# Package the JAR
|
||||
mvn package
|
||||
|
||||
# Install to local repository
|
||||
mvn install
|
||||
|
||||
# Run checkstyle verification
|
||||
mvn checkstyle:check
|
||||
|
||||
# Generate Javadoc
|
||||
mvn javadoc:javadoc
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
The project includes basic unit tests using JUnit 5. The main test class `QwenCodeCliTest` demonstrates how to use the SDK to make simple queries to the Qwen Code CLI.
|
||||
|
||||
### Code Quality
|
||||
|
||||
The project uses Checkstyle for code formatting and style enforcement. The configuration is defined in `checkstyle.xml` and includes rules for:
|
||||
|
||||
- Whitespace and indentation
|
||||
- Naming conventions
|
||||
- Import ordering
|
||||
- Code structure
|
||||
- Line endings (LF only)
|
||||
- No trailing whitespace
|
||||
- 8-space indentation for line wrapping
|
||||
|
||||
## Development Conventions
|
||||
|
||||
### Coding Standards
|
||||
|
||||
- Java 8 language features are supported
|
||||
- Follow standard Java naming conventions
|
||||
- Use UTF-8 encoding for source files
|
||||
- Line endings should be LF (Unix-style)
|
||||
- No trailing whitespace allowed
|
||||
- Use 8-space indentation for line wrapping
|
||||
|
||||
### Testing Practices
|
||||
|
||||
- Write unit tests using JUnit 5
|
||||
- Test classes should be in the `src/test/java` directory
|
||||
- Follow the naming convention `*Test.java` for test classes
|
||||
- Use appropriate assertions to validate functionality
|
||||
|
||||
### Documentation
|
||||
|
||||
- API documentation should follow JavaDoc conventions
|
||||
- Update README files when adding new features
|
||||
- Include examples in documentation
|
||||
|
||||
## API Reference
|
||||
|
||||
### QwenCodeCli Class
|
||||
|
||||
The main class provides several primary methods:
|
||||
|
||||
- `simpleQuery(String prompt)`: Synchronous method that returns a list of responses
|
||||
- `simpleQuery(String prompt, TransportOptions transportOptions)`: Synchronous method with custom transport options
|
||||
- `simpleQuery(String prompt, TransportOptions transportOptions, AssistantContentConsumers assistantContentConsumers)`: Advanced method with custom content consumers
|
||||
- `newSession()`: Creates a new session with default options
|
||||
- `newSession(TransportOptions transportOptions)`: Creates a new session with custom options
|
||||
|
||||
### Permission Modes
|
||||
|
||||
The SDK supports different permission modes for controlling tool execution:
|
||||
|
||||
- **`default`**: Write tools are denied unless approved via `canUseTool` callback or in `allowedTools`. Read-only tools execute without confirmation.
|
||||
- **`plan`**: Blocks all write tools, instructing AI to present a plan first.
|
||||
- **`auto-edit`**: Auto-approve edit tools (edit, write_file) while other tools require confirmation.
|
||||
- **`yolo`**: All tools execute automatically without confirmation.
|
||||
|
||||
### Transport Options
|
||||
|
||||
The `TransportOptions` class allows configuration of how the SDK communicates with the Qwen Code CLI:
|
||||
|
||||
- `pathToQwenExecutable`: Path to the Qwen Code CLI executable
|
||||
- `cwd`: Working directory for the CLI process
|
||||
- `model`: AI model to use for the session
|
||||
- `permissionMode`: Permission mode that controls tool execution
|
||||
- `env`: Environment variables to pass to the CLI process
|
||||
- `maxSessionTurns`: Limits the number of conversation turns in a session
|
||||
- `coreTools`: List of core tools that should be available to the AI
|
||||
- `excludeTools`: List of tools to exclude from being available to the AI
|
||||
- `allowedTools`: List of tools that are pre-approved for use without additional confirmation
|
||||
- `authType`: Authentication type to use for the session
|
||||
- `includePartialMessages`: Enables receiving partial messages during streaming responses
|
||||
- `skillsEnable`: Enables or disables skills functionality for the session
|
||||
- `turnTimeout`: Timeout for a complete turn of conversation
|
||||
- `messageTimeout`: Timeout for individual messages within a turn
|
||||
- `resumeSessionId`: ID of a previous session to resume
|
||||
- `otherOptions`: Additional command-line options to pass to the CLI
|
||||
|
||||
### Session Control Features
|
||||
|
||||
- **Session creation**: Use `QwenCodeCli.newSession()` to create a new session with custom options
|
||||
- **Session management**: The `Session` class provides methods to send prompts, handle responses, and manage session state
|
||||
- **Session cleanup**: Always close sessions using `session.close()` to properly terminate the CLI process
|
||||
- **Session resumption**: Use `setResumeSessionId()` in `TransportOptions` to resume a previous session
|
||||
- **Session interruption**: Use `session.interrupt()` to interrupt a currently running prompt
|
||||
- **Dynamic model switching**: Use `session.setModel()` to change the model during a session
|
||||
- **Dynamic permission mode switching**: Use `session.setPermissionMode()` to change the permission mode during a session
|
||||
|
||||
### Thread Pool Configuration
|
||||
|
||||
The SDK uses a thread pool for managing concurrent operations with the following default configuration:
|
||||
|
||||
- **Core Pool Size**: 30 threads
|
||||
- **Maximum Pool Size**: 100 threads
|
||||
- **Keep-Alive Time**: 60 seconds
|
||||
- **Queue Capacity**: 300 tasks (using LinkedBlockingQueue)
|
||||
- **Thread Naming**: "qwen_code_cli-pool-{number}"
|
||||
- **Daemon Threads**: false
|
||||
- **Rejected Execution Handler**: CallerRunsPolicy
|
||||
|
||||
### Session Event Consumers and Assistant Content Consumers
|
||||
|
||||
The SDK provides two key interfaces for handling events and content from the CLI:
|
||||
|
||||
#### SessionEventConsumers Interface
|
||||
|
||||
The `SessionEventConsumers` interface provides callbacks for different types of messages during a session:
|
||||
|
||||
- `onSystemMessage`: Handles system messages from the CLI (receives Session and SDKSystemMessage)
|
||||
- `onResultMessage`: Handles result messages from the CLI (receives Session and SDKResultMessage)
|
||||
- `onAssistantMessage`: Handles assistant messages (AI responses) (receives Session and SDKAssistantMessage)
|
||||
- `onPartialAssistantMessage`: Handles partial assistant messages during streaming (receives Session and SDKPartialAssistantMessage)
|
||||
- `onUserMessage`: Handles user messages (receives Session and SDKUserMessage)
|
||||
- `onOtherMessage`: Handles other types of messages (receives Session and String message)
|
||||
- `onControlResponse`: Handles control responses (receives Session and CLIControlResponse)
|
||||
- `onControlRequest`: Handles control requests (receives Session and CLIControlRequest, returns CLIControlResponse)
|
||||
- `onPermissionRequest`: Handles permission requests (receives Session and CLIControlRequest<CLIControlPermissionRequest>, returns Behavior)
|
||||
|
||||
#### AssistantContentConsumers Interface
|
||||
|
||||
The `AssistantContentConsumers` interface handles different types of content within assistant messages:
|
||||
|
||||
- `onText`: Handles text content (receives Session and TextAssistantContent)
|
||||
- `onThinking`: Handles thinking content (receives Session and ThingkingAssistantContent)
|
||||
- `onToolUse`: Handles tool use content (receives Session and ToolUseAssistantContent)
|
||||
- `onToolResult`: Handles tool result content (receives Session and ToolResultAssistantContent)
|
||||
- `onOtherContent`: Handles other content types (receives Session and AssistantContent)
|
||||
- `onUsage`: Handles usage information (receives Session and AssistantUsage)
|
||||
- `onPermissionRequest`: Handles permission requests (receives Session and CLIControlPermissionRequest, returns Behavior)
|
||||
- `onOtherControlRequest`: Handles other control requests (receives Session and ControlRequestPayload, returns ControlResponsePayload)
|
||||
|
||||
#### Relationship Between the Interfaces
|
||||
|
||||
**Important Note on Event Hierarchy:**
|
||||
|
||||
- `SessionEventConsumers` is the **high-level** event processor that handles different message types (system, assistant, user, etc.)
|
||||
- `AssistantContentConsumers` is the **low-level** content processor that handles different types of content within assistant messages (text, tools, thinking, etc.)
|
||||
|
||||
**Processor Relationship:**
|
||||
|
||||
- `SessionEventConsumers` → `AssistantContentConsumers` (SessionEventConsumers uses AssistantContentConsumers to process content within assistant messages)
|
||||
|
||||
**Event Derivation Relationships:**
|
||||
|
||||
- `onAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent`, `onUsage`
|
||||
- `onPartialAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent`
|
||||
- `onControlRequest` → `onPermissionRequest`, `onOtherControlRequest`
|
||||
|
||||
**Event Timeout Relationships:**
|
||||
|
||||
Each event handler method has a corresponding timeout method that allows customizing the timeout behavior for that specific event:
|
||||
|
||||
- `onSystemMessage` ↔ `onSystemMessageTimeout`
|
||||
- `onResultMessage` ↔ `onResultMessageTimeout`
|
||||
- `onAssistantMessage` ↔ `onAssistantMessageTimeout`
|
||||
- `onPartialAssistantMessage` ↔ `onPartialAssistantMessageTimeout`
|
||||
- `onUserMessage` ↔ `onUserMessageTimeout`
|
||||
- `onOtherMessage` ↔ `onOtherMessageTimeout`
|
||||
- `onControlResponse` ↔ `onControlResponseTimeout`
|
||||
- `onControlRequest` ↔ `onControlRequestTimeout`
|
||||
|
||||
For AssistantContentConsumers timeout methods:
|
||||
|
||||
- `onText` ↔ `onTextTimeout`
|
||||
- `onThinking` ↔ `onThinkingTimeout`
|
||||
- `onToolUse` ↔ `onToolUseTimeout`
|
||||
- `onToolResult` ↔ `onToolResultTimeout`
|
||||
- `onOtherContent` ↔ `onOtherContentTimeout`
|
||||
- `onPermissionRequest` ↔ `onPermissionRequestTimeout`
|
||||
- `onOtherControlRequest` ↔ `onOtherControlRequestTimeout`
|
||||
|
||||
**Default Timeout Values:**
|
||||
|
||||
- `SessionEventSimpleConsumers` default timeout: 180 seconds (Timeout.TIMEOUT_180_SECONDS)
|
||||
- `AssistantContentSimpleConsumers` default timeout: 60 seconds (Timeout.TIMEOUT_60_SECONDS)
|
||||
|
||||
**Timeout Hierarchy Requirements:**
|
||||
|
||||
For proper operation, the following timeout relationships should be maintained:
|
||||
|
||||
- `onAssistantMessageTimeout` return value should be greater than `onTextTimeout`, `onThinkingTimeout`, `onToolUseTimeout`, `onToolResultTimeout`, and `onOtherContentTimeout` return values
|
||||
- `onControlRequestTimeout` return value should be greater than `onPermissionRequestTimeout` and `onOtherControlRequestTimeout` return values
|
||||
|
||||
#### Relationship Between the Interfaces
|
||||
|
||||
- `AssistantContentSimpleConsumers` is the default implementation of `AssistantContentConsumers`
|
||||
- `SessionEventSimpleConsumers` is the concrete implementation that combines both interfaces and depends on an `AssistantContentConsumers` instance to handle content within assistant messages
|
||||
- The timeout methods in `SessionEventConsumers` now include the message object as a parameter (e.g., `onSystemMessageTimeout(Session session, SDKSystemMessage systemMessage)`)
|
||||
|
||||
Event processing is subject to the timeout settings configured in `TransportOptions` and `SessionEventConsumers`. For detailed timeout configuration options, see the "Timeout" section above.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
The SDK includes several example files in `src/test/java/com/alibaba/qwen/code/cli/example/` that demonstrate different aspects of the API:
|
||||
|
||||
### Basic Usage
|
||||
|
||||
- `QuickStartExample.java`: Demonstrates simple query usage, transport options configuration, and streaming content handling
|
||||
|
||||
### Session Control
|
||||
|
||||
- `SessionExample.java`: Shows session control features including permission mode changes, model switching, interruption, and event handling
|
||||
|
||||
### Configuration
|
||||
|
||||
- `ThreadPoolConfigurationExample.java`: Shows how to configure the thread pool used by the SDK
|
||||
|
||||
## Error Handling
|
||||
|
||||
The SDK provides specific exception types for different error scenarios:
|
||||
|
||||
- `SessionControlException`: Thrown when there's an issue with session control (creation, initialization, etc.)
|
||||
- `SessionSendPromptException`: Thrown when there's an issue sending a prompt or receiving a response
|
||||
- `SessionClosedException`: Thrown when attempting to use a closed session
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── example/
|
||||
│ └── java/
|
||||
│ └── com/
|
||||
│ └── alibaba/
|
||||
│ └── qwen/
|
||||
│ └── code/
|
||||
│ └── example/
|
||||
├── main/
|
||||
│ └── java/
|
||||
│ └── com/
|
||||
│ └── alibaba/
|
||||
│ └── qwen/
|
||||
│ └── code/
|
||||
│ └── cli/
|
||||
│ ├── QwenCodeCli.java
|
||||
│ ├── protocol/
|
||||
│ ├── session/
|
||||
│ ├── transport/
|
||||
│ └── utils/
|
||||
└── test/
|
||||
├── java/
|
||||
│ └── com/
|
||||
│ └── alibaba/
|
||||
│ └── qwen/
|
||||
│ └── code/
|
||||
│ └── cli/
|
||||
│ ├── QwenCodeCliTest.java
|
||||
│ ├── session/
|
||||
│ │ └── SessionTest.java
|
||||
│ └── transport/
|
||||
│ ├── PermissionModeTest.java
|
||||
│ └── process/
|
||||
│ └── ProcessTransportTest.java
|
||||
└── temp/
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
- `pom.xml`: Maven build configuration and dependencies
|
||||
- `checkstyle.xml`: Code style and formatting rules
|
||||
- `.editorconfig`: Editor configuration settings
|
||||
|
||||
## FAQ / Troubleshooting
|
||||
|
||||
### Q: Do I need to install the Qwen CLI separately?
|
||||
|
||||
A: No, from v0.1.1, the CLI is bundled with the SDK, so no standalone CLI installation is needed.
|
||||
|
||||
### Q: What Java versions are supported?
|
||||
|
||||
A: The SDK requires Java 1.8 or higher.
|
||||
|
||||
### Q: How do I handle long-running requests?
|
||||
|
||||
A: The SDK includes timeout utilities. You can configure timeouts using the `Timeout` class in `TransportOptions`.
|
||||
|
||||
### Q: Why are some tools not executing?
|
||||
|
||||
A: This is likely due to permission modes. Check your permission mode settings and consider using `allowedTools` to pre-approve certain tools.
|
||||
|
||||
### Q: How do I resume a previous session?
|
||||
|
||||
A: Use the `setResumeSessionId()` method in `TransportOptions` to resume a previous session.
|
||||
|
||||
### Q: Can I customize the environment for the CLI process?
|
||||
|
||||
A: Yes, use the `setEnv()` method in `TransportOptions` to pass environment variables to the CLI process.
|
||||
|
||||
### Q: What happens if the CLI process crashes?
|
||||
|
||||
A: The SDK will throw appropriate exceptions. Make sure to handle `SessionControlException` and implement retry logic if needed.
|
||||
|
||||
## Maintainers
|
||||
|
||||
- **Developer**: skyfire (gengwei.gw(at)alibaba-inc.com)
|
||||
- **Organization**: Alibaba Group
|
||||
312
packages/sdk-java/README.md
Normal file
312
packages/sdk-java/README.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# Qwen Code Java SDK
|
||||
|
||||
The Qwen Code Java SDK is a minimum experimental SDK for programmatic access to Qwen Code functionality. It provides a Java interface to interact with the Qwen Code CLI, allowing developers to integrate Qwen Code capabilities into their Java applications.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Java >= 1.8
|
||||
- Maven >= 3.6.0 (for building from source)
|
||||
- qwen-code >= 0.5.0
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **Logging**: ch.qos.logback:logback-classic
|
||||
- **Utilities**: org.apache.commons:commons-lang3
|
||||
- **JSON Processing**: com.alibaba.fastjson2:fastjson2
|
||||
- **Testing**: JUnit 5 (org.junit.jupiter:junit-jupiter)
|
||||
|
||||
## Installation
|
||||
|
||||
Add the following dependency to your Maven `pom.xml`:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>qwencode-sdk</artifactId>
|
||||
<version>{$version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Or if using Gradle, add to your `build.gradle`:
|
||||
|
||||
```gradle
|
||||
implementation 'com.alibaba:qwencode-sdk:{$version}'
|
||||
```
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Build Commands
|
||||
|
||||
```bash
|
||||
# Compile the project
|
||||
mvn compile
|
||||
|
||||
# Run tests
|
||||
mvn test
|
||||
|
||||
# Package the JAR
|
||||
mvn package
|
||||
|
||||
# Install to local repository
|
||||
mvn install
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
The simplest way to use the SDK is through the `QwenCodeCli.simpleQuery()` method:
|
||||
|
||||
```java
|
||||
public static void runSimpleExample() {
|
||||
List<String> result = QwenCodeCli.simpleQuery("hello world");
|
||||
result.forEach(logger::info);
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage with custom transport options:
|
||||
|
||||
```java
|
||||
public static void runTransportOptionsExample() {
|
||||
TransportOptions options = new TransportOptions()
|
||||
.setModel("qwen3-coder-flash")
|
||||
.setPermissionMode(PermissionMode.AUTO_EDIT)
|
||||
.setCwd("./")
|
||||
.setEnv(new HashMap<String, String>() {{put("CUSTOM_VAR", "value");}})
|
||||
.setIncludePartialMessages(true)
|
||||
.setTurnTimeout(new Timeout(120L, TimeUnit.SECONDS))
|
||||
.setMessageTimeout(new Timeout(90L, TimeUnit.SECONDS))
|
||||
.setAllowedTools(Arrays.asList("read_file", "write_file", "list_directory"));
|
||||
|
||||
List<String> result = QwenCodeCli.simpleQuery("who are you, what are your capabilities?", options);
|
||||
result.forEach(logger::info);
|
||||
}
|
||||
```
|
||||
|
||||
For streaming content handling with custom content consumers:
|
||||
|
||||
```java
|
||||
public static void runStreamingExample() {
|
||||
QwenCodeCli.simpleQuery("who are you, what are your capabilities?",
|
||||
new TransportOptions().setMessageTimeout(new Timeout(10L, TimeUnit.SECONDS)), new AssistantContentSimpleConsumers() {
|
||||
|
||||
@Override
|
||||
public void onText(Session session, TextAssistantContent textAssistantContent) {
|
||||
logger.info("Text content received: {}", textAssistantContent.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) {
|
||||
logger.info("Thinking content received: {}", thingkingAssistantContent.getThinking());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolUse(Session session, ToolUseAssistantContent toolUseContent) {
|
||||
logger.info("Tool use content received: {} with arguments: {}",
|
||||
toolUseContent, toolUseContent.getInput());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolResult(Session session, ToolResultAssistantContent toolResultContent) {
|
||||
logger.info("Tool result content received: {}", toolResultContent.getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOtherContent(Session session, AssistantContent<?> other) {
|
||||
logger.info("Other content received: {}", other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsage(Session session, AssistantUsage assistantUsage) {
|
||||
logger.info("Usage information received: Input tokens: {}, Output tokens: {}",
|
||||
assistantUsage.getUsage().getInputTokens(), assistantUsage.getUsage().getOutputTokens());
|
||||
}
|
||||
}.setDefaultPermissionOperation(Operation.allow));
|
||||
logger.info("Streaming example completed.");
|
||||
}
|
||||
```
|
||||
|
||||
other examples see src/test/java/com/alibaba/qwen/code/cli/example
|
||||
|
||||
## Architecture
|
||||
|
||||
The SDK follows a layered architecture:
|
||||
|
||||
- **API Layer**: Provides the main entry points through `QwenCodeCli` class with simple static methods for basic usage
|
||||
- **Session Layer**: Manages communication sessions with the Qwen Code CLI through the `Session` class
|
||||
- **Transport Layer**: Handles the communication mechanism between the SDK and CLI process (currently using process transport via `ProcessTransport`)
|
||||
- **Protocol Layer**: Defines data structures for communication based on the CLI protocol
|
||||
- **Utils**: Common utilities for concurrent execution, timeout handling, and error management
|
||||
|
||||
## Key Features
|
||||
|
||||
### Permission Modes
|
||||
|
||||
The SDK supports different permission modes for controlling tool execution:
|
||||
|
||||
- **`default`**: Write tools are denied unless approved via `canUseTool` callback or in `allowedTools`. Read-only tools execute without confirmation.
|
||||
- **`plan`**: Blocks all write tools, instructing AI to present a plan first.
|
||||
- **`auto-edit`**: Auto-approve edit tools (edit, write_file) while other tools require confirmation.
|
||||
- **`yolo`**: All tools execute automatically without confirmation.
|
||||
|
||||
### Session Event Consumers and Assistant Content Consumers
|
||||
|
||||
The SDK provides two key interfaces for handling events and content from the CLI:
|
||||
|
||||
#### SessionEventConsumers Interface
|
||||
|
||||
The `SessionEventConsumers` interface provides callbacks for different types of messages during a session:
|
||||
|
||||
- `onSystemMessage`: Handles system messages from the CLI (receives Session and SDKSystemMessage)
|
||||
- `onResultMessage`: Handles result messages from the CLI (receives Session and SDKResultMessage)
|
||||
- `onAssistantMessage`: Handles assistant messages (AI responses) (receives Session and SDKAssistantMessage)
|
||||
- `onPartialAssistantMessage`: Handles partial assistant messages during streaming (receives Session and SDKPartialAssistantMessage)
|
||||
- `onUserMessage`: Handles user messages (receives Session and SDKUserMessage)
|
||||
- `onOtherMessage`: Handles other types of messages (receives Session and String message)
|
||||
- `onControlResponse`: Handles control responses (receives Session and CLIControlResponse)
|
||||
- `onControlRequest`: Handles control requests (receives Session and CLIControlRequest, returns CLIControlResponse)
|
||||
- `onPermissionRequest`: Handles permission requests (receives Session and CLIControlRequest<CLIControlPermissionRequest>, returns Behavior)
|
||||
|
||||
#### AssistantContentConsumers Interface
|
||||
|
||||
The `AssistantContentConsumers` interface handles different types of content within assistant messages:
|
||||
|
||||
- `onText`: Handles text content (receives Session and TextAssistantContent)
|
||||
- `onThinking`: Handles thinking content (receives Session and ThingkingAssistantContent)
|
||||
- `onToolUse`: Handles tool use content (receives Session and ToolUseAssistantContent)
|
||||
- `onToolResult`: Handles tool result content (receives Session and ToolResultAssistantContent)
|
||||
- `onOtherContent`: Handles other content types (receives Session and AssistantContent)
|
||||
- `onUsage`: Handles usage information (receives Session and AssistantUsage)
|
||||
- `onPermissionRequest`: Handles permission requests (receives Session and CLIControlPermissionRequest, returns Behavior)
|
||||
- `onOtherControlRequest`: Handles other control requests (receives Session and ControlRequestPayload, returns ControlResponsePayload)
|
||||
|
||||
#### Relationship Between the Interfaces
|
||||
|
||||
**Important Note on Event Hierarchy:**
|
||||
|
||||
- `SessionEventConsumers` is the **high-level** event processor that handles different message types (system, assistant, user, etc.)
|
||||
- `AssistantContentConsumers` is the **low-level** content processor that handles different types of content within assistant messages (text, tools, thinking, etc.)
|
||||
|
||||
**Processor Relationship:**
|
||||
|
||||
- `SessionEventConsumers` → `AssistantContentConsumers` (SessionEventConsumers uses AssistantContentConsumers to process content within assistant messages)
|
||||
|
||||
**Event Derivation Relationships:**
|
||||
|
||||
- `onAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent`, `onUsage`
|
||||
- `onPartialAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent`
|
||||
- `onControlRequest` → `onPermissionRequest`, `onOtherControlRequest`
|
||||
|
||||
**Event Timeout Relationships:**
|
||||
|
||||
Each event handler method has a corresponding timeout method that allows customizing the timeout behavior for that specific event:
|
||||
|
||||
- `onSystemMessage` ↔ `onSystemMessageTimeout`
|
||||
- `onResultMessage` ↔ `onResultMessageTimeout`
|
||||
- `onAssistantMessage` ↔ `onAssistantMessageTimeout`
|
||||
- `onPartialAssistantMessage` ↔ `onPartialAssistantMessageTimeout`
|
||||
- `onUserMessage` ↔ `onUserMessageTimeout`
|
||||
- `onOtherMessage` ↔ `onOtherMessageTimeout`
|
||||
- `onControlResponse` ↔ `onControlResponseTimeout`
|
||||
- `onControlRequest` ↔ `onControlRequestTimeout`
|
||||
|
||||
For AssistantContentConsumers timeout methods:
|
||||
|
||||
- `onText` ↔ `onTextTimeout`
|
||||
- `onThinking` ↔ `onThinkingTimeout`
|
||||
- `onToolUse` ↔ `onToolUseTimeout`
|
||||
- `onToolResult` ↔ `onToolResultTimeout`
|
||||
- `onOtherContent` ↔ `onOtherContentTimeout`
|
||||
- `onPermissionRequest` ↔ `onPermissionRequestTimeout`
|
||||
- `onOtherControlRequest` ↔ `onOtherControlRequestTimeout`
|
||||
|
||||
**Default Timeout Values:**
|
||||
|
||||
- `SessionEventSimpleConsumers` default timeout: 180 seconds (Timeout.TIMEOUT_180_SECONDS)
|
||||
- `AssistantContentSimpleConsumers` default timeout: 60 seconds (Timeout.TIMEOUT_60_SECONDS)
|
||||
|
||||
**Timeout Hierarchy Requirements:**
|
||||
|
||||
For proper operation, the following timeout relationships should be maintained:
|
||||
|
||||
- `onAssistantMessageTimeout` return value should be greater than `onTextTimeout`, `onThinkingTimeout`, `onToolUseTimeout`, `onToolResultTimeout`, and `onOtherContentTimeout` return values
|
||||
- `onControlRequestTimeout` return value should be greater than `onPermissionRequestTimeout` and `onOtherControlRequestTimeout` return values
|
||||
|
||||
### Transport Options
|
||||
|
||||
The `TransportOptions` class allows configuration of how the SDK communicates with the Qwen Code CLI:
|
||||
|
||||
- `pathToQwenExecutable`: Path to the Qwen Code CLI executable
|
||||
- `cwd`: Working directory for the CLI process
|
||||
- `model`: AI model to use for the session
|
||||
- `permissionMode`: Permission mode that controls tool execution
|
||||
- `env`: Environment variables to pass to the CLI process
|
||||
- `maxSessionTurns`: Limits the number of conversation turns in a session
|
||||
- `coreTools`: List of core tools that should be available to the AI
|
||||
- `excludeTools`: List of tools to exclude from being available to the AI
|
||||
- `allowedTools`: List of tools that are pre-approved for use without additional confirmation
|
||||
- `authType`: Authentication type to use for the session
|
||||
- `includePartialMessages`: Enables receiving partial messages during streaming responses
|
||||
- `skillsEnable`: Enables or disables skills functionality for the session
|
||||
- `turnTimeout`: Timeout for a complete turn of conversation
|
||||
- `messageTimeout`: Timeout for individual messages within a turn
|
||||
- `resumeSessionId`: ID of a previous session to resume
|
||||
- `otherOptions`: Additional command-line options to pass to the CLI
|
||||
|
||||
### Session Control Features
|
||||
|
||||
- **Session creation**: Use `QwenCodeCli.newSession()` to create a new session with custom options
|
||||
- **Session management**: The `Session` class provides methods to send prompts, handle responses, and manage session state
|
||||
- **Session cleanup**: Always close sessions using `session.close()` to properly terminate the CLI process
|
||||
- **Session resumption**: Use `setResumeSessionId()` in `TransportOptions` to resume a previous session
|
||||
- **Session interruption**: Use `session.interrupt()` to interrupt a currently running prompt
|
||||
- **Dynamic model switching**: Use `session.setModel()` to change the model during a session
|
||||
- **Dynamic permission mode switching**: Use `session.setPermissionMode()` to change the permission mode during a session
|
||||
|
||||
### Thread Pool Configuration
|
||||
|
||||
The SDK uses a thread pool for managing concurrent operations with the following default configuration:
|
||||
|
||||
- **Core Pool Size**: 30 threads
|
||||
- **Maximum Pool Size**: 100 threads
|
||||
- **Keep-Alive Time**: 60 seconds
|
||||
- **Queue Capacity**: 300 tasks (using LinkedBlockingQueue)
|
||||
- **Thread Naming**: "qwen_code_cli-pool-{number}"
|
||||
- **Daemon Threads**: false
|
||||
- **Rejected Execution Handler**: CallerRunsPolicy
|
||||
|
||||
## Error Handling
|
||||
|
||||
The SDK provides specific exception types for different error scenarios:
|
||||
|
||||
- `SessionControlException`: Thrown when there's an issue with session control (creation, initialization, etc.)
|
||||
- `SessionSendPromptException`: Thrown when there's an issue sending a prompt or receiving a response
|
||||
- `SessionClosedException`: Thrown when attempting to use a closed session
|
||||
|
||||
## FAQ / Troubleshooting
|
||||
|
||||
### Q: Do I need to install the Qwen CLI separately?
|
||||
|
||||
A: No, from v0.1.1, the CLI is bundled with the SDK, so no standalone CLI installation is needed.
|
||||
|
||||
### Q: What Java versions are supported?
|
||||
|
||||
A: The SDK requires Java 1.8 or higher.
|
||||
|
||||
### Q: How do I handle long-running requests?
|
||||
|
||||
A: The SDK includes timeout utilities. You can configure timeouts using the `Timeout` class in `TransportOptions`.
|
||||
|
||||
### Q: Why are some tools not executing?
|
||||
|
||||
A: This is likely due to permission modes. Check your permission mode settings and consider using `allowedTools` to pre-approve certain tools.
|
||||
|
||||
### Q: How do I resume a previous session?
|
||||
|
||||
A: Use the `setResumeSessionId()` method in `TransportOptions` to resume a previous session.
|
||||
|
||||
### Q: Can I customize the environment for the CLI process?
|
||||
|
||||
A: Yes, use the `setEnv()` method in `TransportOptions` to pass environment variables to the CLI process.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0 - see [LICENSE](./LICENSE) for details.
|
||||
131
packages/sdk-java/checkstyle.xml
Normal file
131
packages/sdk-java/checkstyle.xml
Normal file
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
<module name="FileTabCharacter" />
|
||||
<module name="NewlineAtEndOfFile">
|
||||
<property name="lineSeparator" value="lf" />
|
||||
</module>
|
||||
<module name="RegexpMultiline">
|
||||
<property name="format" value="\r" />
|
||||
<property name="message" value="Line contains carriage return" />
|
||||
</module>
|
||||
<module name="RegexpMultiline">
|
||||
<property name="format" value=" \n" />
|
||||
<property name="message" value="Line has trailing whitespace" />
|
||||
</module>
|
||||
<module name="RegexpMultiline">
|
||||
<property name="format" value="\n\n\n" />
|
||||
<property name="message" value="Multiple consecutive blank lines" />
|
||||
</module>
|
||||
<module name="RegexpMultiline">
|
||||
<property name="format" value="\n\n\Z" />
|
||||
<property name="message" value="Blank line before end of file" />
|
||||
</module>
|
||||
|
||||
<module name="RegexpMultiline">
|
||||
<property name="format" value="\{\n\n" />
|
||||
<property name="message" value="Blank line after opening brace" />
|
||||
</module>
|
||||
<module name="RegexpMultiline">
|
||||
<property name="format" value="\n\n\s*\}" />
|
||||
<property name="message" value="Blank line before closing brace" />
|
||||
</module>
|
||||
<module name="RegexpMultiline">
|
||||
<property name="format" value="->\s*\{\s+\}" />
|
||||
<property name="message" value="Whitespace inside empty lambda body" />
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<module name="SuppressWarningsHolder" />
|
||||
|
||||
<module name="EmptyBlock">
|
||||
<property name="option" value="text" />
|
||||
<property name="tokens" value="
|
||||
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_IF,
|
||||
LITERAL_FOR, LITERAL_TRY, LITERAL_WHILE, INSTANCE_INIT, STATIC_INIT" />
|
||||
</module>
|
||||
<module name="EmptyStatement" />
|
||||
<module name="EmptyForInitializerPad" />
|
||||
<module name="MethodParamPad">
|
||||
<property name="allowLineBreaks" value="true" />
|
||||
<property name="option" value="nospace" />
|
||||
</module>
|
||||
<module name="ParenPad" />
|
||||
<module name="TypecastParenPad" />
|
||||
<module name="NeedBraces" />
|
||||
<module name="LeftCurly">
|
||||
<property name="option" value="eol" />
|
||||
<property name="tokens" value="
|
||||
LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR,
|
||||
LITERAL_IF, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE" />
|
||||
</module>
|
||||
<module name="GenericWhitespace" />
|
||||
<module name="WhitespaceAfter" />
|
||||
<module name="NoWhitespaceAfter" />
|
||||
<module name="NoWhitespaceBefore" />
|
||||
<module name="SingleSpaceSeparator" />
|
||||
<module name="Indentation">
|
||||
<property name="throwsIndent" value="8" />
|
||||
<property name="lineWrappingIndentation" value="8" />
|
||||
</module>
|
||||
|
||||
<module name="UpperEll" />
|
||||
<module name="DefaultComesLast" />
|
||||
<module name="ArrayTypeStyle" />
|
||||
<module name="ModifierOrder" />
|
||||
<module name="OneStatementPerLine" />
|
||||
<module name="StringLiteralEquality" />
|
||||
<module name="MutableException" />
|
||||
<module name="EqualsHashCode" />
|
||||
<module name="ExplicitInitialization" />
|
||||
<module name="OneTopLevelClass" />
|
||||
|
||||
<module name="MemberName" />
|
||||
<module name="PackageName" />
|
||||
<module name="ClassTypeParameterName">
|
||||
<property name="format" value="^[A-Z][0-9]?$" />
|
||||
</module>
|
||||
<module name="MethodTypeParameterName">
|
||||
<property name="format" value="^[A-Z][0-9]?$" />
|
||||
</module>
|
||||
<module name="AnnotationUseStyle">
|
||||
<property name="trailingArrayComma" value="ignore" />
|
||||
</module>
|
||||
|
||||
<module name="RedundantImport" />
|
||||
<module name="UnusedImports" />
|
||||
<!-- <module name="ImportOrder">-->
|
||||
<!-- <property name="groups" value="*,javax,java" />-->
|
||||
<!-- <property name="separated" value="true" />-->
|
||||
<!-- <property name="option" value="bottom" />-->
|
||||
<!-- <property name="sortStaticImportsAlphabetically" value="true" />-->
|
||||
<!-- </module>-->
|
||||
|
||||
<module name="WhitespaceAround">
|
||||
<property name="allowEmptyConstructors" value="true" />
|
||||
<property name="allowEmptyMethods" value="true" />
|
||||
<property name="allowEmptyLambdas" value="true" />
|
||||
<property name="ignoreEnhancedForColon" value="false" />
|
||||
<property name="tokens" value="
|
||||
ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN,
|
||||
BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAND,
|
||||
LAMBDA, LE, LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
|
||||
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH,
|
||||
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE,
|
||||
LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL,
|
||||
PLUS, PLUS_ASSIGN, QUESTION, SL, SLIST, SL_ASSIGN, SR, SR_ASSIGN,
|
||||
STAR, STAR_ASSIGN, TYPE_EXTENSION_AND" />
|
||||
</module>
|
||||
|
||||
<module name="WhitespaceAfter" />
|
||||
|
||||
<module name="NoWhitespaceAfter">
|
||||
<property name="tokens" value="DOT" />
|
||||
<property name="allowLineBreaks" value="false" />
|
||||
</module>
|
||||
|
||||
<module name="MissingOverride"/>
|
||||
</module>
|
||||
</module>
|
||||
190
packages/sdk-java/pom.xml
Normal file
190
packages/sdk-java/pom.xml
Normal file
@@ -0,0 +1,190 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>qwencode-sdk</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.0.1-alpha1</version>
|
||||
<name>qwencode-sdk</name>
|
||||
<url>https://maven.apache.org</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache 2</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
<comments>A business-friendly OSS license</comments>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<url>https://github.com/QwenLM/qwen-code</url>
|
||||
<connection>scm:git:https://github.com/QwenLM/qwen-code.git</connection>
|
||||
</scm>
|
||||
<properties>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<checkstyle-maven-plugin.version>3.6.0</checkstyle-maven-plugin.version>
|
||||
<jacoco-maven-plugin.version>0.8.12</jacoco-maven-plugin.version>
|
||||
<junit5.version>5.14.1</junit5.version>
|
||||
<logback-classic.version>1.3.16</logback-classic.version>
|
||||
<fastjson2.version>2.0.60</fastjson2.version>
|
||||
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
|
||||
<central-publishing-maven-plugin.version>9</central-publishing-maven-plugin.version>
|
||||
<maven-source-plugin.version>2</maven-source-plugin.version>
|
||||
<maven-javadoc-plugin.version>2.9.1</maven-javadoc-plugin.version>
|
||||
<maven-gpg-plugin.version>1.5</maven-gpg-plugin.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<type>pom</type>
|
||||
<version>${junit5.version}</version>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>${logback-classic.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.20.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>${checkstyle-maven-plugin.version}</version>
|
||||
<configuration>
|
||||
<configLocation>checkstyle.xml</configLocation>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>${jacoco-maven-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.${central-publishing-maven-plugin.version}.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>central</publishingServerId>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>${maven-source-plugin.version}.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>${maven-javadoc-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>${maven-gpg-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<organization>
|
||||
<name>Alibaba Group</name>
|
||||
<url>https://github.com/alibaba</url>
|
||||
</organization>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>skyfire</id>
|
||||
<name>skyfire</name>
|
||||
<email>gengwei.gw(at)alibaba-inc.com</email>
|
||||
<roles>
|
||||
<role>Developer</role>
|
||||
<role>Designer</role>
|
||||
</roles>
|
||||
<timezone>+8</timezone>
|
||||
<url>https://github.com/gwinthis</url>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>central</id>
|
||||
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>https://central.sonatype.com/service/local/staging/deploy/maven2/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
</project>
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.alibaba.qwen.code.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.AssistantContentConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.AssistantContentSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.transport.Transport;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.transport.process.ProcessTransport;
|
||||
import com.alibaba.qwen.code.cli.utils.MyConcurrentUtils;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Main entry point for interacting with the Qwen Code CLI. Provides static methods for simple queries and session management.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class QwenCodeCli {
|
||||
private static final Logger log = LoggerFactory.getLogger(QwenCodeCli.class);
|
||||
|
||||
/**
|
||||
* Sends a simple query to the Qwen Code CLI and returns a list of responses.
|
||||
*
|
||||
* @param prompt The input prompt to send to the CLI
|
||||
* @return A list of strings representing the CLI's responses
|
||||
*/
|
||||
public static List<String> simpleQuery(String prompt) {
|
||||
return simpleQuery(prompt, new TransportOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a simple query with custom transport options.
|
||||
*
|
||||
* @param prompt The input prompt to send to the CLI
|
||||
* @param transportOptions Configuration options for the transport layer
|
||||
* @return A list of strings representing the CLI's responses
|
||||
*/
|
||||
public static List<String> simpleQuery(String prompt, TransportOptions transportOptions) {
|
||||
final List<String> response = new ArrayList<>();
|
||||
MyConcurrentUtils.runAndWait(() -> simpleQuery(prompt, transportOptions, new AssistantContentSimpleConsumers() {
|
||||
@Override
|
||||
public void onText(Session session, TextAssistantContent textAssistantContent) {
|
||||
response.add(textAssistantContent.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) {
|
||||
response.add(thingkingAssistantContent.getThinking());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) {
|
||||
response.add(JSON.toJSONString(toolUseAssistantContent.getContentOfAssistant()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) {
|
||||
response.add(JSON.toJSONString(toolResultAssistantContent));
|
||||
}
|
||||
|
||||
public void onOtherContent(Session session, AssistantContent<?> other) {
|
||||
response.add(JSON.toJSONString(other.getContentOfAssistant()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsage(Session session, AssistantUsage assistantUsage) {
|
||||
log.info("received usage {} of message {}", assistantUsage.getUsage(), assistantUsage.getMessageId());
|
||||
}
|
||||
}.setDefaultPermissionOperation(Operation.allow)), Timeout.TIMEOUT_30_MINUTES);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a query with custom content consumers.
|
||||
*
|
||||
* @param prompt The input prompt to send to the CLI
|
||||
* @param transportOptions Configuration options for the transport layer
|
||||
* @param assistantContentConsumers Consumers for handling different types of assistant content
|
||||
*/
|
||||
public static void simpleQuery(String prompt, TransportOptions transportOptions, AssistantContentConsumers assistantContentConsumers) {
|
||||
Session session = newSession(transportOptions);
|
||||
try {
|
||||
session.sendPrompt(prompt, new SessionEventSimpleConsumers()
|
||||
.setAssistantContentConsumer(assistantContentConsumers));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("sendPrompt error!", e);
|
||||
} finally {
|
||||
try {
|
||||
session.close();
|
||||
} catch (Exception e) {
|
||||
log.error("close session error!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session with default transport options.
|
||||
*
|
||||
* @return A new Session instance
|
||||
*/
|
||||
public static Session newSession() {
|
||||
return newSession(new TransportOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session with custom transport options.
|
||||
*
|
||||
* @param transportOptions Configuration options for the transport layer
|
||||
* @return A new Session instance
|
||||
*/
|
||||
public static Session newSession(TransportOptions transportOptions) {
|
||||
Transport transport;
|
||||
try {
|
||||
transport = new ProcessTransport(transportOptions);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("initialized ProcessTransport error!", e);
|
||||
}
|
||||
|
||||
Session session;
|
||||
try {
|
||||
session = new Session(transport);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("initialized Session error!", e);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents content from the assistant in a Qwen Code session.
|
||||
*
|
||||
* @param <C> The type of content
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public interface AssistantContent<C> {
|
||||
/**
|
||||
* Gets the type of the assistant content.
|
||||
*
|
||||
* @return The type of the assistant content
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* Gets the actual content from the assistant.
|
||||
*
|
||||
* @return The content from the assistant
|
||||
*/
|
||||
C getContentOfAssistant();
|
||||
|
||||
/**
|
||||
* Gets the message ID associated with this content.
|
||||
*
|
||||
* @return The message ID
|
||||
*/
|
||||
String getMessageId();
|
||||
|
||||
/**
|
||||
* Represents text content from the assistant.
|
||||
*/
|
||||
interface TextAssistantContent extends AssistantContent<String> {
|
||||
/**
|
||||
* Gets the text content.
|
||||
*
|
||||
* @return The text content
|
||||
*/
|
||||
String getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents thinking content from the assistant.
|
||||
*/
|
||||
interface ThingkingAssistantContent extends AssistantContent<String> {
|
||||
/**
|
||||
* Gets the thinking content.
|
||||
*
|
||||
* @return The thinking content
|
||||
*/
|
||||
String getThinking();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents tool use content from the assistant.
|
||||
*/
|
||||
interface ToolUseAssistantContent extends AssistantContent<Map<String, Object>> {
|
||||
/**
|
||||
* Gets the tool input.
|
||||
*
|
||||
* @return The tool input
|
||||
*/
|
||||
Map<String, Object> getInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents tool result content from the assistant.
|
||||
*/
|
||||
interface ToolResultAssistantContent extends AssistantContent<String> {
|
||||
/**
|
||||
* Gets whether the tool result indicates an error.
|
||||
*
|
||||
* @return Whether the tool result indicates an error
|
||||
*/
|
||||
Boolean getIsError();
|
||||
|
||||
/**
|
||||
* Gets the tool result content.
|
||||
*
|
||||
* @return The tool result content
|
||||
*/
|
||||
String getContent();
|
||||
|
||||
/**
|
||||
* Gets the tool use ID.
|
||||
*
|
||||
* @return The tool use ID
|
||||
*/
|
||||
String getToolUseId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
|
||||
/**
|
||||
* Represents usage information for an assistant message.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class AssistantUsage {
|
||||
/**
|
||||
* The ID of the message.
|
||||
*/
|
||||
String messageId;
|
||||
/**
|
||||
* The usage information.
|
||||
*/
|
||||
Usage usage;
|
||||
|
||||
/**
|
||||
* Gets the message ID.
|
||||
*
|
||||
* @return The message ID
|
||||
*/
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message ID.
|
||||
*
|
||||
* @param messageId The message ID
|
||||
*/
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the usage information.
|
||||
*
|
||||
* @return The usage information
|
||||
*/
|
||||
public Usage getUsage() {
|
||||
return usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the usage information.
|
||||
*
|
||||
* @param usage The usage information
|
||||
*/
|
||||
public void setUsage(Usage usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new AssistantUsage instance.
|
||||
*
|
||||
* @param messageId The message ID
|
||||
* @param usage The usage information
|
||||
*/
|
||||
public AssistantUsage(String messageId, Usage usage) {
|
||||
this.messageId = messageId;
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>toString.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String toString() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* Represents a permission denial from the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class CLIPermissionDenial {
|
||||
/**
|
||||
* The name of the denied tool.
|
||||
*/
|
||||
@JSONField(name = "tool_name")
|
||||
private String toolName;
|
||||
|
||||
/**
|
||||
* The ID of the denied tool use.
|
||||
*/
|
||||
@JSONField(name = "tool_use_id")
|
||||
private String toolUseId;
|
||||
|
||||
/**
|
||||
* The input for the denied tool.
|
||||
*/
|
||||
@JSONField(name = "tool_input")
|
||||
private Object toolInput;
|
||||
|
||||
/**
|
||||
* Gets the name of the denied tool.
|
||||
*
|
||||
* @return The name of the denied tool
|
||||
*/
|
||||
public String getToolName() {
|
||||
return toolName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the denied tool.
|
||||
*
|
||||
* @param toolName The name of the denied tool
|
||||
*/
|
||||
public void setToolName(String toolName) {
|
||||
this.toolName = toolName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the denied tool use.
|
||||
*
|
||||
* @return The ID of the denied tool use
|
||||
*/
|
||||
public String getToolUseId() {
|
||||
return toolUseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID of the denied tool use.
|
||||
*
|
||||
* @param toolUseId The ID of the denied tool use
|
||||
*/
|
||||
public void setToolUseId(String toolUseId) {
|
||||
this.toolUseId = toolUseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the input for the denied tool.
|
||||
*
|
||||
* @return The input for the denied tool
|
||||
*/
|
||||
public Object getToolInput() {
|
||||
return toolInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input for the denied tool.
|
||||
*
|
||||
* @param toolInput The input for the denied tool
|
||||
*/
|
||||
public void setToolInput(Object toolInput) {
|
||||
this.toolInput = toolInput;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* Represents the capabilities of the Qwen Code CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class Capabilities {
|
||||
/**
|
||||
* Whether the CLI can handle can_use_tool requests.
|
||||
*/
|
||||
@JSONField(name = "can_handle_can_use_tool")
|
||||
boolean canHandleCanUseTool;
|
||||
|
||||
/**
|
||||
* Whether the CLI can handle hook callbacks.
|
||||
*/
|
||||
@JSONField(name = "can_handle_hook_callback")
|
||||
boolean canHandleHookCallback;
|
||||
|
||||
/**
|
||||
* Whether the CLI can set permission mode.
|
||||
*/
|
||||
@JSONField(name = "can_set_permission_mode")
|
||||
boolean canSetPermissionMode;
|
||||
|
||||
/**
|
||||
* Whether the CLI can set the model.
|
||||
*/
|
||||
@JSONField(name = "can_set_model")
|
||||
boolean canSetModel;
|
||||
|
||||
/**
|
||||
* Whether the CLI can handle MCP messages.
|
||||
*/
|
||||
@JSONField(name = "can_handle_mcp_message")
|
||||
boolean canHandleMcpMessage;
|
||||
|
||||
/**
|
||||
* Checks if the CLI can handle can_use_tool requests.
|
||||
*
|
||||
* @return true if the CLI can handle can_use_tool requests, false otherwise
|
||||
*/
|
||||
public boolean isCanHandleCanUseTool() {
|
||||
return canHandleCanUseTool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the CLI can handle can_use_tool requests.
|
||||
*
|
||||
* @param canHandleCanUseTool Whether the CLI can handle can_use_tool requests
|
||||
*/
|
||||
public void setCanHandleCanUseTool(boolean canHandleCanUseTool) {
|
||||
this.canHandleCanUseTool = canHandleCanUseTool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the CLI can handle hook callbacks.
|
||||
*
|
||||
* @return true if the CLI can handle hook callbacks, false otherwise
|
||||
*/
|
||||
public boolean isCanHandleHookCallback() {
|
||||
return canHandleHookCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the CLI can handle hook callbacks.
|
||||
*
|
||||
* @param canHandleHookCallback Whether the CLI can handle hook callbacks
|
||||
*/
|
||||
public void setCanHandleHookCallback(boolean canHandleHookCallback) {
|
||||
this.canHandleHookCallback = canHandleHookCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the CLI can set permission mode.
|
||||
*
|
||||
* @return true if the CLI can set permission mode, false otherwise
|
||||
*/
|
||||
public boolean isCanSetPermissionMode() {
|
||||
return canSetPermissionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the CLI can set permission mode.
|
||||
*
|
||||
* @param canSetPermissionMode Whether the CLI can set permission mode
|
||||
*/
|
||||
public void setCanSetPermissionMode(boolean canSetPermissionMode) {
|
||||
this.canSetPermissionMode = canSetPermissionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the CLI can set the model.
|
||||
*
|
||||
* @return true if the CLI can set the model, false otherwise
|
||||
*/
|
||||
public boolean isCanSetModel() {
|
||||
return canSetModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the CLI can set the model.
|
||||
*
|
||||
* @param canSetModel Whether the CLI can set the model
|
||||
*/
|
||||
public void setCanSetModel(boolean canSetModel) {
|
||||
this.canSetModel = canSetModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the CLI can handle MCP messages.
|
||||
*
|
||||
* @return true if the CLI can handle MCP messages, false otherwise
|
||||
*/
|
||||
public boolean isCanHandleMcpMessage() {
|
||||
return canHandleMcpMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the CLI can handle MCP messages.
|
||||
*
|
||||
* @param canHandleMcpMessage Whether the CLI can handle MCP messages
|
||||
*/
|
||||
public void setCanHandleMcpMessage(boolean canHandleMcpMessage) {
|
||||
this.canHandleMcpMessage = canHandleMcpMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* Extends the Usage class with additional usage information.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class ExtendedUsage extends Usage {
|
||||
/**
|
||||
* Server tool use information.
|
||||
*/
|
||||
@JSONField(name = "server_tool_use")
|
||||
private ServerToolUse serverToolUse;
|
||||
|
||||
/**
|
||||
* Service tier information.
|
||||
*/
|
||||
@JSONField(name = "service_tier")
|
||||
private String serviceTier;
|
||||
|
||||
/**
|
||||
* Cache creation information.
|
||||
*/
|
||||
@JSONField(name = "cache_creation")
|
||||
private CacheCreation cacheCreation;
|
||||
|
||||
/**
|
||||
* Gets the server tool use information.
|
||||
*
|
||||
* @return The server tool use information
|
||||
*/
|
||||
public ServerToolUse getServerToolUse() {
|
||||
return serverToolUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the server tool use information.
|
||||
*
|
||||
* @param serverToolUse The server tool use information
|
||||
*/
|
||||
public void setServerToolUse(ServerToolUse serverToolUse) {
|
||||
this.serverToolUse = serverToolUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the service tier information.
|
||||
*
|
||||
* @return The service tier information
|
||||
*/
|
||||
public String getServiceTier() {
|
||||
return serviceTier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the service tier information.
|
||||
*
|
||||
* @param serviceTier The service tier information
|
||||
*/
|
||||
public void setServiceTier(String serviceTier) {
|
||||
this.serviceTier = serviceTier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache creation information.
|
||||
*
|
||||
* @return The cache creation information
|
||||
*/
|
||||
public CacheCreation getCacheCreation() {
|
||||
return cacheCreation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache creation information.
|
||||
*
|
||||
* @param cacheCreation The cache creation information
|
||||
*/
|
||||
public void setCacheCreation(CacheCreation cacheCreation) {
|
||||
this.cacheCreation = cacheCreation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents server tool use information.
|
||||
*/
|
||||
public static class ServerToolUse {
|
||||
/**
|
||||
* Number of web search requests.
|
||||
*/
|
||||
@JSONField(name = "web_search_requests")
|
||||
private int webSearchRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents cache creation information.
|
||||
*/
|
||||
public static class CacheCreation {
|
||||
/**
|
||||
* Number of ephemeral 1-hour input tokens.
|
||||
*/
|
||||
@JSONField(name = "ephemeral_1h_input_tokens")
|
||||
private int ephemeral1hInputTokens;
|
||||
|
||||
/**
|
||||
* Number of ephemeral 5-minute input tokens.
|
||||
*/
|
||||
@JSONField(name = "ephemeral_5m_input_tokens")
|
||||
private int ephemeral5mInputTokens;
|
||||
|
||||
/**
|
||||
* Gets the number of ephemeral 1-hour input tokens.
|
||||
*
|
||||
* @return The number of ephemeral 1-hour input tokens
|
||||
*/
|
||||
public int getEphemeral1hInputTokens() {
|
||||
return ephemeral1hInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of ephemeral 1-hour input tokens.
|
||||
*
|
||||
* @param ephemeral1hInputTokens The number of ephemeral 1-hour input tokens
|
||||
*/
|
||||
public void setEphemeral1hInputTokens(int ephemeral1hInputTokens) {
|
||||
this.ephemeral1hInputTokens = ephemeral1hInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of ephemeral 5-minute input tokens.
|
||||
*
|
||||
* @return The number of ephemeral 5-minute input tokens
|
||||
*/
|
||||
public int getEphemeral5mInputTokens() {
|
||||
return ephemeral5mInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of ephemeral 5-minute input tokens.
|
||||
*
|
||||
* @param ephemeral5mInputTokens The number of ephemeral 5-minute input tokens
|
||||
*/
|
||||
public void setEphemeral5mInputTokens(int ephemeral5mInputTokens) {
|
||||
this.ephemeral5mInputTokens = ephemeral5mInputTokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
/**
|
||||
* Configuration for initializing the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class InitializeConfig {
|
||||
/**
|
||||
* Hooks configuration.
|
||||
*/
|
||||
String hooks;
|
||||
/**
|
||||
* SDK MCP servers configuration.
|
||||
*/
|
||||
String sdkMcpServers;
|
||||
/**
|
||||
* MCP servers configuration.
|
||||
*/
|
||||
String mcpServers;
|
||||
/**
|
||||
* Agents configuration.
|
||||
*/
|
||||
String agents;
|
||||
|
||||
/**
|
||||
* Gets the hooks configuration.
|
||||
*
|
||||
* @return The hooks configuration
|
||||
*/
|
||||
public String getHooks() {
|
||||
return hooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hooks configuration.
|
||||
*
|
||||
* @param hooks The hooks configuration
|
||||
*/
|
||||
public void setHooks(String hooks) {
|
||||
this.hooks = hooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SDK MCP servers configuration.
|
||||
*
|
||||
* @return The SDK MCP servers configuration
|
||||
*/
|
||||
public String getSdkMcpServers() {
|
||||
return sdkMcpServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the SDK MCP servers configuration.
|
||||
*
|
||||
* @param sdkMcpServers The SDK MCP servers configuration
|
||||
*/
|
||||
public void setSdkMcpServers(String sdkMcpServers) {
|
||||
this.sdkMcpServers = sdkMcpServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MCP servers configuration.
|
||||
*
|
||||
* @return The MCP servers configuration
|
||||
*/
|
||||
public String getMcpServers() {
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the MCP servers configuration.
|
||||
*
|
||||
* @param mcpServers The MCP servers configuration
|
||||
*/
|
||||
public void setMcpServers(String mcpServers) {
|
||||
this.mcpServers = mcpServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the agents configuration.
|
||||
*
|
||||
* @return The agents configuration
|
||||
*/
|
||||
public String getAgents() {
|
||||
return agents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the agents configuration.
|
||||
*
|
||||
* @param agents The agents configuration
|
||||
*/
|
||||
public void setAgents(String agents) {
|
||||
this.agents = agents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
/**
|
||||
* Represents usage information for a specific model.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class ModelUsage {
|
||||
/**
|
||||
* Number of input tokens.
|
||||
*/
|
||||
private int inputTokens;
|
||||
/**
|
||||
* Number of output tokens.
|
||||
*/
|
||||
private int outputTokens;
|
||||
/**
|
||||
* Number of cache read input tokens.
|
||||
*/
|
||||
private int cacheReadInputTokens;
|
||||
/**
|
||||
* Number of cache creation input tokens.
|
||||
*/
|
||||
private int cacheCreationInputTokens;
|
||||
/**
|
||||
* Number of web search requests.
|
||||
*/
|
||||
private int webSearchRequests;
|
||||
/**
|
||||
* Context window size.
|
||||
*/
|
||||
private int contextWindow;
|
||||
|
||||
/**
|
||||
* Gets the number of input tokens.
|
||||
*
|
||||
* @return The number of input tokens
|
||||
*/
|
||||
public int getInputTokens() {
|
||||
return inputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of input tokens.
|
||||
*
|
||||
* @param inputTokens The number of input tokens
|
||||
*/
|
||||
public void setInputTokens(int inputTokens) {
|
||||
this.inputTokens = inputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of output tokens.
|
||||
*
|
||||
* @return The number of output tokens
|
||||
*/
|
||||
public int getOutputTokens() {
|
||||
return outputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of output tokens.
|
||||
*
|
||||
* @param outputTokens The number of output tokens
|
||||
*/
|
||||
public void setOutputTokens(int outputTokens) {
|
||||
this.outputTokens = outputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of cache read input tokens.
|
||||
*
|
||||
* @return The number of cache read input tokens
|
||||
*/
|
||||
public int getCacheReadInputTokens() {
|
||||
return cacheReadInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of cache read input tokens.
|
||||
*
|
||||
* @param cacheReadInputTokens The number of cache read input tokens
|
||||
*/
|
||||
public void setCacheReadInputTokens(int cacheReadInputTokens) {
|
||||
this.cacheReadInputTokens = cacheReadInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of cache creation input tokens.
|
||||
*
|
||||
* @return The number of cache creation input tokens
|
||||
*/
|
||||
public int getCacheCreationInputTokens() {
|
||||
return cacheCreationInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of cache creation input tokens.
|
||||
*
|
||||
* @param cacheCreationInputTokens The number of cache creation input tokens
|
||||
*/
|
||||
public void setCacheCreationInputTokens(int cacheCreationInputTokens) {
|
||||
this.cacheCreationInputTokens = cacheCreationInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of web search requests.
|
||||
*
|
||||
* @return The number of web search requests
|
||||
*/
|
||||
public int getWebSearchRequests() {
|
||||
return webSearchRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of web search requests.
|
||||
*
|
||||
* @param webSearchRequests The number of web search requests
|
||||
*/
|
||||
public void setWebSearchRequests(int webSearchRequests) {
|
||||
this.webSearchRequests = webSearchRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the context window size.
|
||||
*
|
||||
* @return The context window size
|
||||
*/
|
||||
public int getContextWindow() {
|
||||
return contextWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the context window size.
|
||||
*
|
||||
* @param contextWindow The context window size
|
||||
*/
|
||||
public void setContextWindow(int contextWindow) {
|
||||
this.contextWindow = contextWindow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
/**
|
||||
* Represents different permission modes for the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public enum PermissionMode {
|
||||
/**
|
||||
* Default permission mode.
|
||||
*/
|
||||
DEFAULT("default"),
|
||||
/**
|
||||
* Plan permission mode.
|
||||
*/
|
||||
PLAN("plan"),
|
||||
/**
|
||||
* Auto-edit permission mode.
|
||||
*/
|
||||
AUTO_EDIT("auto-edit"),
|
||||
/**
|
||||
* YOLO permission mode.
|
||||
*/
|
||||
YOLO("yolo");
|
||||
|
||||
private final String value;
|
||||
|
||||
PermissionMode(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string value of the permission mode.
|
||||
*
|
||||
* @return The string value of the permission mode
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permission mode from its string value.
|
||||
*
|
||||
* @param value The string value
|
||||
* @return The corresponding permission mode
|
||||
*/
|
||||
public static PermissionMode fromValue(String value) {
|
||||
for (PermissionMode mode : PermissionMode.values()) {
|
||||
if (mode.value.equals(value)) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown permission mode: " + value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* Represents usage information for a message.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class Usage {
|
||||
/**
|
||||
* Number of input tokens.
|
||||
*/
|
||||
@JSONField(name = "input_tokens")
|
||||
private Integer inputTokens;
|
||||
/**
|
||||
* Number of output tokens.
|
||||
*/
|
||||
@JSONField(name = "output_tokens")
|
||||
private Integer outputTokens;
|
||||
/**
|
||||
* Number of cache creation input tokens.
|
||||
*/
|
||||
@JSONField(name = "cache_creation_input_tokens")
|
||||
private Integer cacheCreationInputTokens;
|
||||
/**
|
||||
* Number of cache read input tokens.
|
||||
*/
|
||||
@JSONField(name = "cache_read_input_tokens")
|
||||
private Integer cacheReadInputTokens;
|
||||
/**
|
||||
* Total number of tokens.
|
||||
*/
|
||||
@JSONField(name = "total_tokens")
|
||||
private Integer totalTokens;
|
||||
|
||||
/**
|
||||
* Gets the number of input tokens.
|
||||
*
|
||||
* @return The number of input tokens
|
||||
*/
|
||||
public Integer getInputTokens() {
|
||||
return inputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of input tokens.
|
||||
*
|
||||
* @param inputTokens The number of input tokens
|
||||
*/
|
||||
public void setInputTokens(Integer inputTokens) {
|
||||
this.inputTokens = inputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of output tokens.
|
||||
*
|
||||
* @return The number of output tokens
|
||||
*/
|
||||
public Integer getOutputTokens() {
|
||||
return outputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of output tokens.
|
||||
*
|
||||
* @param outputTokens The number of output tokens
|
||||
*/
|
||||
public void setOutputTokens(Integer outputTokens) {
|
||||
this.outputTokens = outputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of cache creation input tokens.
|
||||
*
|
||||
* @return The number of cache creation input tokens
|
||||
*/
|
||||
public Integer getCacheCreationInputTokens() {
|
||||
return cacheCreationInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of cache creation input tokens.
|
||||
*
|
||||
* @param cacheCreationInputTokens The number of cache creation input tokens
|
||||
*/
|
||||
public void setCacheCreationInputTokens(Integer cacheCreationInputTokens) {
|
||||
this.cacheCreationInputTokens = cacheCreationInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of cache read input tokens.
|
||||
*
|
||||
* @return The number of cache read input tokens
|
||||
*/
|
||||
public Integer getCacheReadInputTokens() {
|
||||
return cacheReadInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of cache read input tokens.
|
||||
*
|
||||
* @param cacheReadInputTokens The number of cache read input tokens
|
||||
*/
|
||||
public void setCacheReadInputTokens(Integer cacheReadInputTokens) {
|
||||
this.cacheReadInputTokens = cacheReadInputTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of tokens.
|
||||
*
|
||||
* @return The total number of tokens
|
||||
*/
|
||||
public Integer getTotalTokens() {
|
||||
return totalTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total number of tokens.
|
||||
*
|
||||
* @param totalTokens The total number of tokens
|
||||
*/
|
||||
public void setTotalTokens(Integer totalTokens) {
|
||||
this.totalTokens = totalTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>toString.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String toString() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data.behavior;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents an allow behavior that permits an operation.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "operation", typeName = "allow")
|
||||
public class Allow extends Behavior {
|
||||
/**
|
||||
* Creates a new Allow instance and sets the behavior to allow.
|
||||
*/
|
||||
public Allow() {
|
||||
super();
|
||||
this.behavior = Operation.allow;
|
||||
}
|
||||
/**
|
||||
* Updated input for the operation.
|
||||
*/
|
||||
Map<String, Object> updatedInput;
|
||||
|
||||
/**
|
||||
* Gets the updated input.
|
||||
*
|
||||
* @return The updated input
|
||||
*/
|
||||
public Map<String, Object> getUpdatedInput() {
|
||||
return updatedInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the updated input.
|
||||
*
|
||||
* @param updatedInput The updated input
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public Allow setUpdatedInput(Map<String, Object> updatedInput) {
|
||||
this.updatedInput = updatedInput;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data.behavior;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Base class for behavior objects that define how the CLI should handle requests.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "operation", typeName = "Behavior", seeAlso = {Allow.class, Deny.class})
|
||||
public class Behavior {
|
||||
/**
|
||||
* The behavior operation (allow or deny).
|
||||
*/
|
||||
Operation behavior;
|
||||
|
||||
/**
|
||||
* Gets the behavior operation.
|
||||
*
|
||||
* @return The behavior operation
|
||||
*/
|
||||
public Operation getBehavior() {
|
||||
return behavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the behavior operation.
|
||||
*
|
||||
* @param behavior The behavior operation
|
||||
*/
|
||||
public void setBehavior(Operation behavior) {
|
||||
this.behavior = behavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the type of operation.
|
||||
*/
|
||||
public enum Operation {
|
||||
/**
|
||||
* Allow the operation.
|
||||
*/
|
||||
allow,
|
||||
/**
|
||||
* Deny the operation.
|
||||
*/
|
||||
deny
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default behavior (deny with message).
|
||||
*
|
||||
* @return The default behavior
|
||||
*/
|
||||
public static Behavior defaultBehavior() {
|
||||
return denyBehavior();
|
||||
}
|
||||
|
||||
public static Behavior denyBehavior() {
|
||||
return new Deny().setMessage("Default Behavior Permission denied");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data.behavior;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a deny behavior that rejects an operation.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "operation", typeName = "deny")
|
||||
public class Deny extends Behavior {
|
||||
/**
|
||||
* Creates a new Deny instance and sets the behavior to deny.
|
||||
*/
|
||||
public Deny() {
|
||||
super();
|
||||
this.behavior = Operation.deny;
|
||||
}
|
||||
|
||||
/**
|
||||
* The message explaining why the operation was denied.
|
||||
*/
|
||||
String message;
|
||||
|
||||
/**
|
||||
* Gets the denial message.
|
||||
*
|
||||
* @return The denial message
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the denial message.
|
||||
*
|
||||
* @param message The denial message
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public Deny setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message;
|
||||
|
||||
/**
|
||||
* Represents a message in the Qwen Code protocol.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public interface Message {
|
||||
/**
|
||||
* Gets the type of the message.
|
||||
*
|
||||
* @return The type of the message
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* Gets the ID of the message.
|
||||
*
|
||||
* @return The ID of the message
|
||||
*/
|
||||
String getMessageId();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Base class for messages in the Qwen Code protocol.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(alphabetic = false, typeKey = "type", typeName = "MessageBase")
|
||||
public class MessageBase implements Message{
|
||||
/**
|
||||
* The type of the message.
|
||||
*/
|
||||
protected String type;
|
||||
|
||||
/**
|
||||
* The ID of the message.
|
||||
*/
|
||||
@JSONField(name = "message_id")
|
||||
protected String messageId;
|
||||
|
||||
/**
|
||||
* <p>toString.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String toString() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of the message.
|
||||
*
|
||||
* @param type The type of the message
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID of the message.
|
||||
*
|
||||
* @param messageId The ID of the message
|
||||
*/
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.CLIPermissionDenial;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.ExtendedUsage;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.Usage;
|
||||
|
||||
/**
|
||||
* Represents a result message from the SDK.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "result")
|
||||
public class SDKResultMessage extends MessageBase {
|
||||
/**
|
||||
* The subtype of the result.
|
||||
*/
|
||||
private String subtype; // 'error_max_turns' | 'error_during_execution'
|
||||
/**
|
||||
* The UUID of the message.
|
||||
*/
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* The session ID.
|
||||
*/
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
|
||||
/**
|
||||
* Whether the result represents an error.
|
||||
*/
|
||||
@JSONField(name = "is_error")
|
||||
private boolean isError = true;
|
||||
|
||||
/**
|
||||
* Duration in milliseconds.
|
||||
*/
|
||||
@JSONField(name = "duration_ms")
|
||||
private Long durationMs;
|
||||
|
||||
/**
|
||||
* API duration in milliseconds.
|
||||
*/
|
||||
@JSONField(name = "duration_api_ms")
|
||||
private Long durationApiMs;
|
||||
|
||||
/**
|
||||
* Number of turns.
|
||||
*/
|
||||
@JSONField(name = "num_turns")
|
||||
private Integer numTurns;
|
||||
/**
|
||||
* Usage information.
|
||||
*/
|
||||
private ExtendedUsage usage;
|
||||
/**
|
||||
* Model usage information.
|
||||
*/
|
||||
private Map<String, Usage> modelUsage;
|
||||
|
||||
/**
|
||||
* List of permission denials.
|
||||
*/
|
||||
@JSONField(name = "permission_denials")
|
||||
private List<CLIPermissionDenial> permissionDenials;
|
||||
/**
|
||||
* Error information.
|
||||
*/
|
||||
private Error error;
|
||||
|
||||
/**
|
||||
* Creates a new SDKResultMessage instance and sets the type to "result".
|
||||
*/
|
||||
public SDKResultMessage() {
|
||||
super();
|
||||
this.type = "result";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subtype of the result.
|
||||
*
|
||||
* @return The subtype of the result
|
||||
*/
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtype of the result.
|
||||
*
|
||||
* @param subtype The subtype of the result
|
||||
*/
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID of the message.
|
||||
*
|
||||
* @return The UUID of the message
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UUID of the message.
|
||||
*
|
||||
* @param uuid The UUID of the message
|
||||
*/
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session ID.
|
||||
*
|
||||
* @return The session ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session ID.
|
||||
*
|
||||
* @param sessionId The session ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the result represents an error.
|
||||
*
|
||||
* @return Whether the result represents an error
|
||||
*/
|
||||
public boolean isError() {
|
||||
return isError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the result represents an error.
|
||||
*
|
||||
* @param error Whether the result represents an error
|
||||
*/
|
||||
public void setError(boolean error) {
|
||||
isError = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the duration in milliseconds.
|
||||
*
|
||||
* @return The duration in milliseconds
|
||||
*/
|
||||
public Long getDurationMs() {
|
||||
return durationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration in milliseconds.
|
||||
*
|
||||
* @param durationMs The duration in milliseconds
|
||||
*/
|
||||
public void setDurationMs(Long durationMs) {
|
||||
this.durationMs = durationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API duration in milliseconds.
|
||||
*
|
||||
* @return The API duration in milliseconds
|
||||
*/
|
||||
public Long getDurationApiMs() {
|
||||
return durationApiMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API duration in milliseconds.
|
||||
*
|
||||
* @param durationApiMs The API duration in milliseconds
|
||||
*/
|
||||
public void setDurationApiMs(Long durationApiMs) {
|
||||
this.durationApiMs = durationApiMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of turns.
|
||||
*
|
||||
* @return The number of turns
|
||||
*/
|
||||
public Integer getNumTurns() {
|
||||
return numTurns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of turns.
|
||||
*
|
||||
* @param numTurns The number of turns
|
||||
*/
|
||||
public void setNumTurns(Integer numTurns) {
|
||||
this.numTurns = numTurns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the usage information.
|
||||
*
|
||||
* @return The usage information
|
||||
*/
|
||||
public ExtendedUsage getUsage() {
|
||||
return usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the usage information.
|
||||
*
|
||||
* @param usage The usage information
|
||||
*/
|
||||
public void setUsage(ExtendedUsage usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model usage information.
|
||||
*
|
||||
* @return The model usage information
|
||||
*/
|
||||
public Map<String, Usage> getModelUsage() {
|
||||
return modelUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model usage information.
|
||||
*
|
||||
* @param modelUsage The model usage information
|
||||
*/
|
||||
public void setModelUsage(Map<String, Usage> modelUsage) {
|
||||
this.modelUsage = modelUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of permission denials.
|
||||
*
|
||||
* @return The list of permission denials
|
||||
*/
|
||||
public List<CLIPermissionDenial> getPermissionDenials() {
|
||||
return permissionDenials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of permission denials.
|
||||
*
|
||||
* @param permissionDenials The list of permission denials
|
||||
*/
|
||||
public void setPermissionDenials(List<CLIPermissionDenial> permissionDenials) {
|
||||
this.permissionDenials = permissionDenials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error information.
|
||||
*
|
||||
* @return The error information
|
||||
*/
|
||||
public Error getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error information.
|
||||
*
|
||||
* @param error The error information
|
||||
*/
|
||||
public void setError(Error error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents error information.
|
||||
*/
|
||||
public static class Error {
|
||||
/**
|
||||
* Error type.
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* Error message.
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* Gets the error type.
|
||||
*
|
||||
* @return The error type
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error type.
|
||||
*
|
||||
* @param type The error type
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error message.
|
||||
*
|
||||
* @return The error message
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error message.
|
||||
*
|
||||
* @param message The error message
|
||||
*/
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a system message from the SDK.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "system")
|
||||
public class SDKSystemMessage extends MessageBase {
|
||||
/**
|
||||
* The subtype of the system message.
|
||||
*/
|
||||
private String subtype;
|
||||
/**
|
||||
* The UUID of the message.
|
||||
*/
|
||||
private String uuid;
|
||||
/**
|
||||
* The session ID.
|
||||
*/
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
/**
|
||||
* Additional data.
|
||||
*/
|
||||
private Object data;
|
||||
/**
|
||||
* Current working directory.
|
||||
*/
|
||||
private String cwd;
|
||||
/**
|
||||
* List of available tools.
|
||||
*/
|
||||
private List<String> tools;
|
||||
/**
|
||||
* List of MCP servers.
|
||||
*/
|
||||
@JSONField(name = "mcp_servers")
|
||||
private List<McpServer> mcpServers;
|
||||
/**
|
||||
* Model information.
|
||||
*/
|
||||
private String model;
|
||||
/**
|
||||
* Permission mode.
|
||||
*/
|
||||
@JSONField(name = "permission_mode")
|
||||
private String permissionMode;
|
||||
/**
|
||||
* Available slash commands.
|
||||
*/
|
||||
@JSONField(name = "slash_commands")
|
||||
private List<String> slashCommands;
|
||||
/**
|
||||
* Qwen Code version.
|
||||
*/
|
||||
@JSONField(name = "qwen_code_version")
|
||||
private String qwenCodeVersion;
|
||||
/**
|
||||
* Output style.
|
||||
*/
|
||||
@JSONField(name = "output_style")
|
||||
private String outputStyle;
|
||||
/**
|
||||
* Available agents.
|
||||
*/
|
||||
private List<String> agents;
|
||||
/**
|
||||
* Available skills.
|
||||
*/
|
||||
private List<String> skills;
|
||||
/**
|
||||
* Capabilities information.
|
||||
*/
|
||||
private Map<String, Object> capabilities;
|
||||
/**
|
||||
* Compact metadata.
|
||||
*/
|
||||
@JSONField(name = "compact_metadata")
|
||||
private CompactMetadata compactMetadata;
|
||||
|
||||
/**
|
||||
* Creates a new SDKSystemMessage instance and sets the type to "system".
|
||||
*/
|
||||
public SDKSystemMessage() {
|
||||
super();
|
||||
this.type = "system";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subtype of the system message.
|
||||
*
|
||||
* @return The subtype of the system message
|
||||
*/
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtype of the system message.
|
||||
*
|
||||
* @param subtype The subtype of the system message
|
||||
*/
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID of the message.
|
||||
*
|
||||
* @return The UUID of the message
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UUID of the message.
|
||||
*
|
||||
* @param uuid The UUID of the message
|
||||
*/
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session ID.
|
||||
*
|
||||
* @return The session ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session ID.
|
||||
*
|
||||
* @param sessionId The session ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the additional data.
|
||||
*
|
||||
* @return The additional data
|
||||
*/
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the additional data.
|
||||
*
|
||||
* @param data The additional data
|
||||
*/
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current working directory.
|
||||
*
|
||||
* @return The current working directory
|
||||
*/
|
||||
public String getCwd() {
|
||||
return cwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current working directory.
|
||||
*
|
||||
* @param cwd The current working directory
|
||||
*/
|
||||
public void setCwd(String cwd) {
|
||||
this.cwd = cwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of available tools.
|
||||
*
|
||||
* @return The list of available tools
|
||||
*/
|
||||
public List<String> getTools() {
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of available tools.
|
||||
*
|
||||
* @param tools The list of available tools
|
||||
*/
|
||||
public void setTools(List<String> tools) {
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of MCP servers.
|
||||
*
|
||||
* @return The list of MCP servers
|
||||
*/
|
||||
public List<McpServer> getMcpServers() {
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of MCP servers.
|
||||
*
|
||||
* @param mcpServers The list of MCP servers
|
||||
*/
|
||||
public void setMcpServers(List<McpServer> mcpServers) {
|
||||
this.mcpServers = mcpServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model information.
|
||||
*
|
||||
* @return The model information
|
||||
*/
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model information.
|
||||
*
|
||||
* @param model The model information
|
||||
*/
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permission mode.
|
||||
*
|
||||
* @return The permission mode
|
||||
*/
|
||||
public String getPermissionMode() {
|
||||
return permissionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the permission mode.
|
||||
*
|
||||
* @param permissionMode The permission mode
|
||||
*/
|
||||
public void setPermissionMode(String permissionMode) {
|
||||
this.permissionMode = permissionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the available slash commands.
|
||||
*
|
||||
* @return The available slash commands
|
||||
*/
|
||||
public List<String> getSlashCommands() {
|
||||
return slashCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the available slash commands.
|
||||
*
|
||||
* @param slashCommands The available slash commands
|
||||
*/
|
||||
public void setSlashCommands(List<String> slashCommands) {
|
||||
this.slashCommands = slashCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Qwen Code version.
|
||||
*
|
||||
* @return The Qwen Code version
|
||||
*/
|
||||
public String getQwenCodeVersion() {
|
||||
return qwenCodeVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Qwen Code version.
|
||||
*
|
||||
* @param qwenCodeVersion The Qwen Code version
|
||||
*/
|
||||
public void setQwenCodeVersion(String qwenCodeVersion) {
|
||||
this.qwenCodeVersion = qwenCodeVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output style.
|
||||
*
|
||||
* @return The output style
|
||||
*/
|
||||
public String getOutputStyle() {
|
||||
return outputStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the output style.
|
||||
*
|
||||
* @param outputStyle The output style
|
||||
*/
|
||||
public void setOutputStyle(String outputStyle) {
|
||||
this.outputStyle = outputStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the available agents.
|
||||
*
|
||||
* @return The available agents
|
||||
*/
|
||||
public List<String> getAgents() {
|
||||
return agents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the available agents.
|
||||
*
|
||||
* @param agents The available agents
|
||||
*/
|
||||
public void setAgents(List<String> agents) {
|
||||
this.agents = agents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the available skills.
|
||||
*
|
||||
* @return The available skills
|
||||
*/
|
||||
public List<String> getSkills() {
|
||||
return skills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the available skills.
|
||||
*
|
||||
* @param skills The available skills
|
||||
*/
|
||||
public void setSkills(List<String> skills) {
|
||||
this.skills = skills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the capabilities information.
|
||||
*
|
||||
* @return The capabilities information
|
||||
*/
|
||||
public Map<String, Object> getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the capabilities information.
|
||||
*
|
||||
* @param capabilities The capabilities information
|
||||
*/
|
||||
public void setCapabilities(Map<String, Object> capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compact metadata.
|
||||
*
|
||||
* @return The compact metadata
|
||||
*/
|
||||
public CompactMetadata getCompactMetadata() {
|
||||
return compactMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compact metadata.
|
||||
*
|
||||
* @param compactMetadata The compact metadata
|
||||
*/
|
||||
public void setCompactMetadata(CompactMetadata compactMetadata) {
|
||||
this.compactMetadata = compactMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents MCP server information.
|
||||
*/
|
||||
public static class McpServer {
|
||||
/**
|
||||
* Server name.
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* Server status.
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* Gets the server name.
|
||||
*
|
||||
* @return The server name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the server name.
|
||||
*
|
||||
* @param name The server name
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server status.
|
||||
*
|
||||
* @return The server status
|
||||
*/
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the server status.
|
||||
*
|
||||
* @param status The server status
|
||||
*/
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents compact metadata.
|
||||
*/
|
||||
public static class CompactMetadata {
|
||||
/**
|
||||
* Trigger information.
|
||||
*/
|
||||
private String trigger;
|
||||
|
||||
/**
|
||||
* Pre-tokens information.
|
||||
*/
|
||||
@JSONField(name = "pre_tokens")
|
||||
private Integer preTokens;
|
||||
|
||||
/**
|
||||
* Gets the trigger information.
|
||||
*
|
||||
* @return The trigger information
|
||||
*/
|
||||
public String getTrigger() {
|
||||
return trigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the trigger information.
|
||||
*
|
||||
* @param trigger The trigger information
|
||||
*/
|
||||
public void setTrigger(String trigger) {
|
||||
this.trigger = trigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pre-tokens information.
|
||||
*
|
||||
* @return The pre-tokens information
|
||||
*/
|
||||
public Integer getPreTokens() {
|
||||
return preTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pre-tokens information.
|
||||
*
|
||||
* @param preTokens The pre-tokens information
|
||||
*/
|
||||
public void setPreTokens(Integer preTokens) {
|
||||
this.preTokens = preTokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a user message in the SDK protocol.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "user")
|
||||
public class SDKUserMessage extends MessageBase {
|
||||
/**
|
||||
* The UUID of the message.
|
||||
*/
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* The session ID.
|
||||
*/
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
/**
|
||||
* The API user message.
|
||||
*/
|
||||
private final APIUserMessage message = new APIUserMessage();
|
||||
|
||||
/**
|
||||
* The parent tool use ID.
|
||||
*/
|
||||
@JSONField(name = "parent_tool_use_id")
|
||||
private String parentToolUseId;
|
||||
/**
|
||||
* Additional options.
|
||||
*/
|
||||
private Map<String, String> options;
|
||||
|
||||
/**
|
||||
* Creates a new SDKUserMessage instance and sets the type to "user".
|
||||
*/
|
||||
public SDKUserMessage() {
|
||||
super();
|
||||
this.setType("user");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID of the message.
|
||||
*
|
||||
* @return The UUID of the message
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UUID of the message.
|
||||
*
|
||||
* @param uuid The UUID of the message
|
||||
*/
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session ID.
|
||||
*
|
||||
* @return The session ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session ID.
|
||||
*
|
||||
* @param sessionId The session ID
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public SDKUserMessage setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content of the message.
|
||||
*
|
||||
* @param content The content of the message
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public SDKUserMessage setContent(String content) {
|
||||
message.setContent(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content of the message.
|
||||
*
|
||||
* @return The content of the message
|
||||
*/
|
||||
public String getContent() {
|
||||
return message.getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent tool use ID.
|
||||
*
|
||||
* @return The parent tool use ID
|
||||
*/
|
||||
public String getParentToolUseId() {
|
||||
return parentToolUseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent tool use ID.
|
||||
*
|
||||
* @param parentToolUseId The parent tool use ID
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public SDKUserMessage setParentToolUseId(String parentToolUseId) {
|
||||
this.parentToolUseId = parentToolUseId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the additional options.
|
||||
*
|
||||
* @return The additional options
|
||||
*/
|
||||
public Map<String, String> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the additional options.
|
||||
*
|
||||
* @param options The additional options
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public SDKUserMessage setOptions(Map<String, String> options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the API user message.
|
||||
*/
|
||||
public static class APIUserMessage {
|
||||
/**
|
||||
* User role.
|
||||
*/
|
||||
private String role = "user";
|
||||
/**
|
||||
* Message content.
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* Gets the user role.
|
||||
*
|
||||
* @return The user role
|
||||
*/
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user role.
|
||||
*
|
||||
* @param role The user role
|
||||
*/
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message content.
|
||||
*
|
||||
* @return The message content
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message content.
|
||||
*
|
||||
* @param content The message content
|
||||
*/
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.Usage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock;
|
||||
|
||||
/**
|
||||
* Represents an API assistant message.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class APIAssistantMessage {
|
||||
/**
|
||||
* Message ID.
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* Message type.
|
||||
*/
|
||||
private String type = "message";
|
||||
/**
|
||||
* Message role.
|
||||
*/
|
||||
private String role = "assistant";
|
||||
/**
|
||||
* Message model.
|
||||
*/
|
||||
private String model;
|
||||
/**
|
||||
* Message content.
|
||||
*/
|
||||
private List<ContentBlock<?>> content;
|
||||
|
||||
/**
|
||||
* Stop reason.
|
||||
*/
|
||||
@JSONField(name = "stop_reason")
|
||||
private String stopReason;
|
||||
/**
|
||||
* Usage information.
|
||||
*/
|
||||
private Usage usage;
|
||||
|
||||
/**
|
||||
* Gets the message ID.
|
||||
*
|
||||
* @return The message ID
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message ID.
|
||||
*
|
||||
* @param id The message ID
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message type.
|
||||
*
|
||||
* @return The message type
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message type.
|
||||
*
|
||||
* @param type The message type
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message role.
|
||||
*
|
||||
* @return The message role
|
||||
*/
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message role.
|
||||
*
|
||||
* @param role The message role
|
||||
*/
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message model.
|
||||
*
|
||||
* @return The message model
|
||||
*/
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message model.
|
||||
*
|
||||
* @param model The message model
|
||||
*/
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stop reason.
|
||||
*
|
||||
* @return The stop reason
|
||||
*/
|
||||
public String getStopReason() {
|
||||
return stopReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stop reason.
|
||||
*
|
||||
* @param stopReason The stop reason
|
||||
*/
|
||||
public void setStopReason(String stopReason) {
|
||||
this.stopReason = stopReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the usage information.
|
||||
*
|
||||
* @return The usage information
|
||||
*/
|
||||
public Usage getUsage() {
|
||||
return usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the usage information.
|
||||
*
|
||||
* @param usage The usage information
|
||||
*/
|
||||
public void setUsage(Usage usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message content.
|
||||
*
|
||||
* @return The message content
|
||||
*/
|
||||
public List<ContentBlock<?>> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message content.
|
||||
*
|
||||
* @param content The message content
|
||||
*/
|
||||
public void setContent(List<ContentBlock<?>> content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.MessageBase;
|
||||
|
||||
/**
|
||||
* Represents an SDK assistant message.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "assistant")
|
||||
public class SDKAssistantMessage extends MessageBase {
|
||||
/**
|
||||
* The UUID of the message.
|
||||
*/
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* The session ID.
|
||||
*/
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
/**
|
||||
* The API assistant message.
|
||||
*/
|
||||
private APIAssistantMessage message;
|
||||
|
||||
/**
|
||||
* The parent tool use ID.
|
||||
*/
|
||||
@JSONField(name = "parent_tool_use_id")
|
||||
private String parentToolUseId;
|
||||
|
||||
/**
|
||||
* Creates a new SDKAssistantMessage instance and sets the type to "assistant".
|
||||
*/
|
||||
public SDKAssistantMessage() {
|
||||
super();
|
||||
this.type = "assistant";
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getMessageId() {
|
||||
return this.getUuid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID of the message.
|
||||
*
|
||||
* @return The UUID of the message
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UUID of the message.
|
||||
*
|
||||
* @param uuid The UUID of the message
|
||||
*/
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session ID.
|
||||
*
|
||||
* @return The session ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session ID.
|
||||
*
|
||||
* @param sessionId The session ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API assistant message.
|
||||
*
|
||||
* @return The API assistant message
|
||||
*/
|
||||
public APIAssistantMessage getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API assistant message.
|
||||
*
|
||||
* @param message The API assistant message
|
||||
*/
|
||||
public void setMessage(APIAssistantMessage message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent tool use ID.
|
||||
*
|
||||
* @return The parent tool use ID
|
||||
*/
|
||||
public String getParentToolUseId() {
|
||||
return parentToolUseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent tool use ID.
|
||||
*
|
||||
* @param parentToolUseId The parent tool use ID
|
||||
*/
|
||||
public void setParentToolUseId(String parentToolUseId) {
|
||||
this.parentToolUseId = parentToolUseId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.MessageBase;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.event.StreamEvent;
|
||||
|
||||
/**
|
||||
* Represents a partial assistant message during streaming.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "stream_event")
|
||||
public class SDKPartialAssistantMessage extends MessageBase {
|
||||
/**
|
||||
* The UUID of the message.
|
||||
*/
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* The session ID.
|
||||
*/
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
/**
|
||||
* The stream event.
|
||||
*/
|
||||
private StreamEvent event;
|
||||
|
||||
/**
|
||||
* The parent tool use ID.
|
||||
*/
|
||||
@JSONField(name = "parent_tool_use_id")
|
||||
private String parentToolUseId;
|
||||
|
||||
/**
|
||||
* Creates a new SDKPartialAssistantMessage instance and sets the type to "stream_event".
|
||||
*/
|
||||
public SDKPartialAssistantMessage() {
|
||||
super();
|
||||
this.type = "stream_event";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UUID of the message.
|
||||
*
|
||||
* @return The UUID of the message
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UUID of the message.
|
||||
*
|
||||
* @param uuid The UUID of the message
|
||||
*/
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session ID.
|
||||
*
|
||||
* @return The session ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session ID.
|
||||
*
|
||||
* @param sessionId The session ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stream event.
|
||||
*
|
||||
* @return The stream event
|
||||
*/
|
||||
public StreamEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stream event.
|
||||
*
|
||||
* @param event The stream event
|
||||
*/
|
||||
public void setEvent(StreamEvent event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent tool use ID.
|
||||
*
|
||||
* @return The parent tool use ID
|
||||
*/
|
||||
public String getParentToolUseId() {
|
||||
return parentToolUseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent tool use ID.
|
||||
*
|
||||
* @param parentToolUseId The parent tool use ID
|
||||
*/
|
||||
public void setParentToolUseId(String parentToolUseId) {
|
||||
this.parentToolUseId = parentToolUseId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* Represents an annotation for a content block.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class Annotation {
|
||||
/**
|
||||
* The annotation type.
|
||||
*/
|
||||
@JSONField(name = "type")
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* The annotation value.
|
||||
*/
|
||||
@JSONField(name = "value")
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* Gets the annotation type.
|
||||
*
|
||||
* @return The annotation type
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the annotation type.
|
||||
*
|
||||
* @param type The annotation type
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the annotation value.
|
||||
*
|
||||
* @return The annotation value
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the annotation value.
|
||||
*
|
||||
* @param value The annotation value
|
||||
*/
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
|
||||
/**
|
||||
* Abstract base class for content blocks in assistant messages.
|
||||
*
|
||||
* @param <C> The type of content
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "ContentBlock", seeAlso = { TextBlock.class, ToolResultBlock.class, ThinkingBlock.class, ToolUseBlock.class })
|
||||
public abstract class ContentBlock<C> implements AssistantContent<C> {
|
||||
/**
|
||||
* The type of the content block.
|
||||
*/
|
||||
protected String type;
|
||||
/**
|
||||
* List of annotations.
|
||||
*/
|
||||
protected List<Annotation> annotations;
|
||||
/**
|
||||
* The message ID.
|
||||
*/
|
||||
protected String messageId;
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of the content block.
|
||||
*
|
||||
* @param type The type of the content block
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of annotations.
|
||||
*
|
||||
* @return The list of annotations
|
||||
*/
|
||||
public List<Annotation> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of annotations.
|
||||
*
|
||||
* @param annotations The list of annotations
|
||||
*/
|
||||
public void setAnnotations(List<Annotation> annotations) {
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message ID.
|
||||
*
|
||||
* @param messageId The message ID
|
||||
*/
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>toString.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String toString() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent;
|
||||
|
||||
/**
|
||||
* Represents a text content block.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "text")
|
||||
public class TextBlock extends ContentBlock<String> implements TextAssistantContent {
|
||||
/**
|
||||
* The text content.
|
||||
*/
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* Gets the text content.
|
||||
*
|
||||
* @return The text content
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text content.
|
||||
*
|
||||
* @param text The text content
|
||||
*/
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getContentOfAssistant() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent;
|
||||
|
||||
/**
|
||||
* Represents a thinking content block.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "thinking")
|
||||
public class ThinkingBlock extends ContentBlock<String> implements ThingkingAssistantContent {
|
||||
/**
|
||||
* The thinking content.
|
||||
*/
|
||||
private String thinking;
|
||||
/**
|
||||
* The signature.
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* Gets the thinking content.
|
||||
*
|
||||
* @return The thinking content
|
||||
*/
|
||||
public String getThinking() {
|
||||
return thinking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the thinking content.
|
||||
*
|
||||
* @param thinking The thinking content
|
||||
*/
|
||||
public void setThinking(String thinking) {
|
||||
this.thinking = thinking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the signature.
|
||||
*
|
||||
* @return The signature
|
||||
*/
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the signature.
|
||||
*
|
||||
* @param signature The signature
|
||||
*/
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getContentOfAssistant() {
|
||||
return thinking;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent;
|
||||
|
||||
/**
|
||||
* Represents a tool result content block.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "tool_result")
|
||||
public class ToolResultBlock extends ContentBlock<String> implements ToolResultAssistantContent {
|
||||
/**
|
||||
* The tool use ID.
|
||||
*/
|
||||
@JSONField(name = "tool_use_id")
|
||||
private String toolUseId;
|
||||
|
||||
/**
|
||||
* The result content.
|
||||
*/
|
||||
@JSONField(name = "content")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* Whether the result is an error.
|
||||
*/
|
||||
@JSONField(name = "is_error")
|
||||
private Boolean isError;
|
||||
|
||||
/**
|
||||
* Gets the tool use ID.
|
||||
*
|
||||
* @return The tool use ID
|
||||
*/
|
||||
public String getToolUseId() {
|
||||
return toolUseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tool use ID.
|
||||
*
|
||||
* @param toolUseId The tool use ID
|
||||
*/
|
||||
public void setToolUseId(String toolUseId) {
|
||||
this.toolUseId = toolUseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result content.
|
||||
*
|
||||
* @return The result content
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the result content.
|
||||
*
|
||||
* @param content The result content
|
||||
*/
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the result is an error.
|
||||
*
|
||||
* @return Whether the result is an error
|
||||
*/
|
||||
public Boolean getIsError() {
|
||||
return isError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the result is an error.
|
||||
*
|
||||
* @param isError Whether the result is an error
|
||||
*/
|
||||
public void setIsError(Boolean isError) {
|
||||
this.isError = isError;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String getContentOfAssistant() {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent;
|
||||
|
||||
/**
|
||||
* Represents a tool use content block.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "tool_use")
|
||||
public class ToolUseBlock extends ContentBlock<Map<String, Object>> implements ToolUseAssistantContent {
|
||||
/**
|
||||
* The tool use ID.
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* The tool name.
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* The tool input.
|
||||
*/
|
||||
private Map<String, Object> input;
|
||||
/**
|
||||
* List of annotations.
|
||||
*/
|
||||
private List<Annotation> annotations;
|
||||
|
||||
/**
|
||||
* Creates a new ToolUseBlock instance.
|
||||
*/
|
||||
public ToolUseBlock() {}
|
||||
|
||||
/**
|
||||
* Gets the tool use ID.
|
||||
*
|
||||
* @return The tool use ID
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tool use ID.
|
||||
*
|
||||
* @param id The tool use ID
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tool name.
|
||||
*
|
||||
* @return The tool name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tool name.
|
||||
*
|
||||
* @param name The tool name
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tool input.
|
||||
*
|
||||
* @return The tool input
|
||||
*/
|
||||
public Map<String, Object> getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tool input.
|
||||
*
|
||||
* @param input The tool input
|
||||
*/
|
||||
public void setInput(Map<String, Object> input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of annotations.
|
||||
*
|
||||
* @return The list of annotations
|
||||
*/
|
||||
public List<Annotation> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Sets the list of annotations.
|
||||
*/
|
||||
@Override
|
||||
public void setAnnotations(List<Annotation> annotations) {
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Gets the content of the assistant.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getContentOfAssistant() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent;
|
||||
|
||||
/**
|
||||
* Represents a content block delta event during streaming.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "content_block_delta")
|
||||
public class ContentBlockDeltaEvent extends StreamEvent {
|
||||
/**
|
||||
* The index of the content block.
|
||||
*/
|
||||
private int index;
|
||||
/**
|
||||
* The content block delta.
|
||||
*/
|
||||
private ContentBlockDelta<?> delta;
|
||||
|
||||
/**
|
||||
* Gets the index of the content block.
|
||||
*
|
||||
* @return The index of the content block
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index of the content block.
|
||||
*
|
||||
* @param index The index of the content block
|
||||
*/
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content block delta.
|
||||
*
|
||||
* @return The content block delta
|
||||
*/
|
||||
public ContentBlockDelta<?> getDelta() {
|
||||
return delta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content block delta.
|
||||
*
|
||||
* @param delta The content block delta
|
||||
*/
|
||||
public void setDelta(ContentBlockDelta<?> delta) {
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for content block deltas.
|
||||
*
|
||||
* @param <C> The type of content
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "ContentBlockDelta",
|
||||
seeAlso = {ContentBlockDeltaText.class, ContentBlockDeltaThinking.class, ContentBlockDeltaInputJson.class})
|
||||
public abstract static class ContentBlockDelta<C> implements AssistantContent<C> {
|
||||
/**
|
||||
* The type of the content block delta.
|
||||
*/
|
||||
protected String type;
|
||||
/**
|
||||
* The message ID.
|
||||
*/
|
||||
protected String messageId;
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of the content block delta.
|
||||
*
|
||||
* @param type The type of the content block delta
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message ID.
|
||||
*
|
||||
* @param messageId The message ID
|
||||
*/
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a text delta.
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "text_delta")
|
||||
public static class ContentBlockDeltaText extends ContentBlockDelta<String> implements TextAssistantContent {
|
||||
/**
|
||||
* The text content.
|
||||
*/
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* Gets the text content.
|
||||
*
|
||||
* @return The text content
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text content.
|
||||
*
|
||||
* @param text The text content
|
||||
*/
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentOfAssistant() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a thinking delta.
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "thinking_delta")
|
||||
public static class ContentBlockDeltaThinking extends ContentBlockDelta<String> implements ThingkingAssistantContent {
|
||||
/**
|
||||
* The thinking content.
|
||||
*/
|
||||
private String thinking;
|
||||
|
||||
/**
|
||||
* Gets the thinking content.
|
||||
*
|
||||
* @return The thinking content
|
||||
*/
|
||||
public String getThinking() {
|
||||
return thinking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the thinking content.
|
||||
*
|
||||
* @param thinking The thinking content
|
||||
*/
|
||||
public void setThinking(String thinking) {
|
||||
this.thinking = thinking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentOfAssistant() {
|
||||
return thinking;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an input JSON delta.
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "input_json_delta")
|
||||
public static class ContentBlockDeltaInputJson extends ContentBlockDelta<Map<String, Object>> implements ToolUseAssistantContent {
|
||||
/**
|
||||
* The partial JSON content.
|
||||
*/
|
||||
@JSONField(name = "partial_json")
|
||||
private String partialJson;
|
||||
|
||||
/**
|
||||
* Gets the partial JSON content.
|
||||
*
|
||||
* @return The partial JSON content
|
||||
*/
|
||||
public String getPartialJson() {
|
||||
return partialJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the partial JSON content.
|
||||
*
|
||||
* @param partialJson The partial JSON content
|
||||
*/
|
||||
public void setPartialJson(String partialJson) {
|
||||
this.partialJson = partialJson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getContentOfAssistant() {
|
||||
return getInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getInput() {
|
||||
return JSON.parseObject(partialJson, new TypeReference<Map<String, Object>>() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock;
|
||||
|
||||
/**
|
||||
* Represents a content block start event during message streaming.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "content_block_start")
|
||||
public class ContentBlockStartEvent extends StreamEvent{
|
||||
/**
|
||||
* The index of the content block.
|
||||
*/
|
||||
private int index;
|
||||
|
||||
/**
|
||||
* The content block that is starting.
|
||||
*/
|
||||
@JSONField(name = "content_block")
|
||||
private ContentBlock contentBlock;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a content block stop event during message streaming.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "content_block_stop")
|
||||
public class ContentBlockStopEvent extends StreamEvent{
|
||||
/**
|
||||
* The index of the content block.
|
||||
*/
|
||||
Long index;
|
||||
|
||||
/**
|
||||
* Gets the index of the content block.
|
||||
*
|
||||
* @return The index of the content block
|
||||
*/
|
||||
public Long getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index of the content block.
|
||||
*
|
||||
* @param index The index of the content block
|
||||
*/
|
||||
public void setIndex(Long index) {
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a message start event during message streaming.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeName = "message_start")
|
||||
public class MessageStartStreamEvent extends StreamEvent{
|
||||
/**
|
||||
* The message that is starting.
|
||||
*/
|
||||
private Message message;
|
||||
|
||||
/**
|
||||
* Represents the message information.
|
||||
*/
|
||||
public static class Message {
|
||||
/**
|
||||
* Message ID.
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* Message role.
|
||||
*/
|
||||
private String role;
|
||||
/**
|
||||
* Message model.
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* Gets the message ID.
|
||||
*
|
||||
* @return The message ID
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message ID.
|
||||
*
|
||||
* @param id The message ID
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message role.
|
||||
*
|
||||
* @return The message role
|
||||
*/
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message role.
|
||||
*
|
||||
* @param role The message role
|
||||
*/
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message model.
|
||||
*
|
||||
* @return The message model
|
||||
*/
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message model.
|
||||
*
|
||||
* @param model The message model
|
||||
*/
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message that is starting.
|
||||
*
|
||||
* @return The message that is starting
|
||||
*/
|
||||
public Message getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message that is starting.
|
||||
*
|
||||
* @param message The message that is starting
|
||||
*/
|
||||
public void setMessage(Message message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a message stop event during message streaming.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeName = "message_stop")
|
||||
public class MessageStopStreamEvent extends StreamEvent{
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Base class for stream events during message streaming.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "StreamEvent",
|
||||
seeAlso = {MessageStartStreamEvent.class, MessageStopStreamEvent.class, ContentBlockStartEvent.class, ContentBlockStopEvent.class,
|
||||
ContentBlockDeltaEvent.class})
|
||||
public class StreamEvent {
|
||||
/**
|
||||
* The type of the stream event.
|
||||
*/
|
||||
protected String type;
|
||||
|
||||
/**
|
||||
* Gets the type of the stream event.
|
||||
*
|
||||
* @return The type of the stream event
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of the stream event.
|
||||
*
|
||||
* @param type The type of the stream event
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.MessageBase;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload;
|
||||
|
||||
/**
|
||||
* Represents a control request to the CLI.
|
||||
*
|
||||
* @param <R> The type of the request object
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "control_request")
|
||||
public class CLIControlRequest<R extends ControlRequestPayload> extends MessageBase {
|
||||
/**
|
||||
* The ID of the request.
|
||||
*/
|
||||
@JSONField(name = "request_id")
|
||||
private String requestId = UUID.randomUUID().toString();
|
||||
|
||||
/**
|
||||
* The actual request object.
|
||||
*/
|
||||
private R request;
|
||||
|
||||
/**
|
||||
* Creates a new CLIControlRequest instance and sets the type to "control_request".
|
||||
*/
|
||||
public CLIControlRequest() {
|
||||
super();
|
||||
type = "control_request";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new control request with the specified request object.
|
||||
*
|
||||
* @param request The request object
|
||||
* @param <T> The type of the request object
|
||||
* @return A new control request instance
|
||||
*/
|
||||
public static <T extends ControlRequestPayload> CLIControlRequest<T> create(T request) {
|
||||
CLIControlRequest<T> controlRequest = new CLIControlRequest<>();
|
||||
controlRequest.setRequest(request);
|
||||
return controlRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the request.
|
||||
*
|
||||
* @return The ID of the request
|
||||
*/
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID of the request.
|
||||
*
|
||||
* @param requestId The ID of the request
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public CLIControlRequest<R> setRequestId(String requestId) {
|
||||
this.requestId = requestId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the actual request object.
|
||||
*
|
||||
* @return The actual request object
|
||||
*/
|
||||
public R getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the actual request object.
|
||||
*
|
||||
* @param request The actual request object
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public CLIControlRequest<R> setRequest(R request) {
|
||||
this.request = request;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.MessageBase;
|
||||
|
||||
/**
|
||||
* Represents a control response from the CLI.
|
||||
*
|
||||
* @param <R> The type of the response object
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "type", typeName = "control_response")
|
||||
public class CLIControlResponse<R> extends MessageBase {
|
||||
/**
|
||||
* The response object.
|
||||
*/
|
||||
private Response<R> response;
|
||||
|
||||
/**
|
||||
* Creates a new CLIControlResponse instance and sets the type to "control_response".
|
||||
*/
|
||||
public CLIControlResponse() {
|
||||
super();
|
||||
this.type = "control_response";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the response object.
|
||||
*
|
||||
* @return The response object
|
||||
*/
|
||||
public Response<R> getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the response object.
|
||||
*
|
||||
* @param response The response object
|
||||
*/
|
||||
public void setResponse(Response<R> response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new response object.
|
||||
*
|
||||
* @return A new response object
|
||||
*/
|
||||
public Response<R> createResponse() {
|
||||
Response<R> response = new Response<>();
|
||||
this.setResponse(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the response information.
|
||||
*
|
||||
* @param <R> The type of the response object
|
||||
*/
|
||||
public static class Response<R> {
|
||||
/**
|
||||
* The ID of the request.
|
||||
*/
|
||||
@JSONField(name = "request_id")
|
||||
private String requestId;
|
||||
/**
|
||||
* The subtype of the response.
|
||||
*/
|
||||
private String subtype = "success";
|
||||
/**
|
||||
* The actual response.
|
||||
*/
|
||||
R response;
|
||||
|
||||
/**
|
||||
* Gets the ID of the request.
|
||||
*
|
||||
* @return The ID of the request
|
||||
*/
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID of the request.
|
||||
*
|
||||
* @param requestId The ID of the request
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public Response<R> setRequestId(String requestId) {
|
||||
this.requestId = requestId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subtype of the response.
|
||||
*
|
||||
* @return The subtype of the response
|
||||
*/
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtype of the response.
|
||||
*
|
||||
* @param subtype The subtype of the response
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public Response<R> setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the actual response.
|
||||
*
|
||||
* @return The actual response
|
||||
*/
|
||||
public R getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the actual response.
|
||||
*
|
||||
* @param response The actual response
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public Response<R> setResponse(R response) {
|
||||
this.response = response;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.InitializeConfig;
|
||||
|
||||
/**
|
||||
* Represents a control initialize request to the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "subtype", typeName = "initialize")
|
||||
public class CLIControlInitializeRequest extends ControlRequestPayload {
|
||||
public CLIControlInitializeRequest() {
|
||||
super();
|
||||
this.subtype = "initialize";
|
||||
}
|
||||
|
||||
/**
|
||||
* The initialization configuration.
|
||||
*/
|
||||
@JSONField(unwrapped = true)
|
||||
InitializeConfig initializeConfig = new InitializeConfig();
|
||||
|
||||
/**
|
||||
* Gets the initialization configuration.
|
||||
*
|
||||
* @return The initialization configuration
|
||||
*/
|
||||
public InitializeConfig getInitializeConfig() {
|
||||
return initializeConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initialization configuration.
|
||||
*
|
||||
* @param initializeConfig The initialization configuration
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public CLIControlInitializeRequest setInitializeConfig(InitializeConfig initializeConfig) {
|
||||
this.initializeConfig = initializeConfig;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.Capabilities;
|
||||
|
||||
/**
|
||||
* Represents a control initialize response from the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "subtype", typeName = "initialize")
|
||||
public class CLIControlInitializeResponse extends ControlResponsePayload {
|
||||
public CLIControlInitializeResponse() {
|
||||
super();
|
||||
this.subtype = "initialize";
|
||||
}
|
||||
|
||||
/**
|
||||
* The capabilities' information.
|
||||
*/
|
||||
Capabilities capabilities;
|
||||
|
||||
/**
|
||||
* Gets the capabilities information.
|
||||
*
|
||||
* @return The capabilities information
|
||||
*/
|
||||
public Capabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the capabilities information.
|
||||
*
|
||||
* @param capabilities The capabilities information
|
||||
*/
|
||||
public void setCapabilities(Capabilities capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a control interrupt request to the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "subtype", typeName = "interrupt")
|
||||
public class CLIControlInterruptRequest extends ControlRequestPayload {
|
||||
public CLIControlInterruptRequest() {
|
||||
super();
|
||||
setSubtype("interrupt");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a control permission request to the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "subtype", typeName = "can_use_tool")
|
||||
public class CLIControlPermissionRequest extends ControlRequestPayload {
|
||||
public CLIControlPermissionRequest() {
|
||||
super();
|
||||
this.subtype = "can_use_tool";
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the tool requesting permission.
|
||||
*/
|
||||
@JSONField(name = "tool_name")
|
||||
private String toolName;
|
||||
|
||||
/**
|
||||
* The ID of the tool use.
|
||||
*/
|
||||
@JSONField(name = "tool_use_id")
|
||||
private String toolUseId;
|
||||
|
||||
/**
|
||||
* The input for the tool.
|
||||
*/
|
||||
private Map<String, Object> input;
|
||||
|
||||
/**
|
||||
* List of permission suggestions.
|
||||
*/
|
||||
@JSONField(name = "permission_suggestions")
|
||||
private List<PermissionSuggestion> permissionSuggestions;
|
||||
|
||||
/**
|
||||
* The blocked path.
|
||||
*/
|
||||
@JSONField(name = "blocked_path")
|
||||
private String blockedPath;
|
||||
|
||||
/**
|
||||
* Gets the name of the tool requesting permission.
|
||||
*
|
||||
* @return The name of the tool requesting permission
|
||||
*/
|
||||
public String getToolName() {
|
||||
return toolName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the tool requesting permission.
|
||||
*
|
||||
* @param toolName The name of the tool requesting permission
|
||||
*/
|
||||
public void setToolName(String toolName) {
|
||||
this.toolName = toolName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the tool use.
|
||||
*
|
||||
* @return The ID of the tool use
|
||||
*/
|
||||
public String getToolUseId() {
|
||||
return toolUseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID of the tool use.
|
||||
*
|
||||
* @param toolUseId The ID of the tool use
|
||||
*/
|
||||
public void setToolUseId(String toolUseId) {
|
||||
this.toolUseId = toolUseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the input for the tool.
|
||||
*
|
||||
* @return The input for the tool
|
||||
*/
|
||||
public Map<String, Object> getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input for the tool.
|
||||
*
|
||||
* @param input The input for the tool
|
||||
*/
|
||||
public void setInput(Map<String, Object> input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of permission suggestions.
|
||||
*
|
||||
* @return The list of permission suggestions
|
||||
*/
|
||||
public List<PermissionSuggestion> getPermissionSuggestions() {
|
||||
return permissionSuggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of permission suggestions.
|
||||
*
|
||||
* @param permissionSuggestions The list of permission suggestions
|
||||
*/
|
||||
public void setPermissionSuggestions(
|
||||
List<PermissionSuggestion> permissionSuggestions) {
|
||||
this.permissionSuggestions = permissionSuggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blocked path.
|
||||
*
|
||||
* @return The blocked path
|
||||
*/
|
||||
public String getBlockedPath() {
|
||||
return blockedPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blocked path.
|
||||
*
|
||||
* @param blockedPath The blocked path
|
||||
*/
|
||||
public void setBlockedPath(String blockedPath) {
|
||||
this.blockedPath = blockedPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a permission suggestion.
|
||||
*/
|
||||
public static class PermissionSuggestion {
|
||||
/**
|
||||
* The type of suggestion (allow, deny, modify).
|
||||
*/
|
||||
private String type; // 'allow' | 'deny' | 'modify'
|
||||
/**
|
||||
* The label for the suggestion.
|
||||
*/
|
||||
private String label;
|
||||
/**
|
||||
* The description of the suggestion.
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* The modified input.
|
||||
*/
|
||||
private Object modifiedInput;
|
||||
|
||||
/**
|
||||
* Gets the type of suggestion.
|
||||
*
|
||||
* @return The type of suggestion
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of suggestion.
|
||||
*
|
||||
* @param type The type of suggestion
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the label for the suggestion.
|
||||
*
|
||||
* @return The label for the suggestion
|
||||
*/
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label for the suggestion.
|
||||
*
|
||||
* @param label The label for the suggestion
|
||||
*/
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the description of the suggestion.
|
||||
*
|
||||
* @return The description of the suggestion
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of the suggestion.
|
||||
*
|
||||
* @param description The description of the suggestion
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the modified input.
|
||||
*
|
||||
* @return The modified input
|
||||
*/
|
||||
public Object getModifiedInput() {
|
||||
return modifiedInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the modified input.
|
||||
*
|
||||
* @param modifiedInput The modified input
|
||||
*/
|
||||
public void setModifiedInput(Object modifiedInput) {
|
||||
this.modifiedInput = modifiedInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
|
||||
|
||||
/**
|
||||
* Represents a control permission response from the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "subtype", typeName = "can_use_tool")
|
||||
public class CLIControlPermissionResponse extends ControlResponsePayload {
|
||||
public CLIControlPermissionResponse() {
|
||||
super();
|
||||
this.subtype = "can_use_tool";
|
||||
}
|
||||
|
||||
/**
|
||||
* The behavior for the permission request.
|
||||
*/
|
||||
@JSONField(unwrapped = true)
|
||||
Behavior behavior;
|
||||
|
||||
/**
|
||||
* Gets the behavior for the permission request.
|
||||
*
|
||||
* @return The behavior for the permission request
|
||||
*/
|
||||
public Behavior getBehavior() {
|
||||
return behavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the behavior for the permission request.
|
||||
*
|
||||
* @param behavior The behavior for the permission request
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public CLIControlPermissionResponse setBehavior(Behavior behavior) {
|
||||
this.behavior = behavior;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a control request to set the model in the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "subtype", typeName = "set_model")
|
||||
public class CLIControlSetModelRequest extends ControlRequestPayload {
|
||||
public CLIControlSetModelRequest() {
|
||||
super();
|
||||
this.subtype = "set_model";
|
||||
}
|
||||
|
||||
/**
|
||||
* The model to set.
|
||||
*/
|
||||
String model;
|
||||
|
||||
/**
|
||||
* Gets the model to set.
|
||||
*
|
||||
* @return The model to set
|
||||
*/
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model to set.
|
||||
*
|
||||
* @param model The model to set
|
||||
*/
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
/**
|
||||
* Represents a control response for setting the model in the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class CLIControlSetModelResponse {
|
||||
/**
|
||||
* The subtype of the response ("set_model").
|
||||
*/
|
||||
String subtype = "set_model";
|
||||
/**
|
||||
* The model that was set.
|
||||
*/
|
||||
String model;
|
||||
|
||||
/**
|
||||
* Gets the subtype of the response.
|
||||
*
|
||||
* @return The subtype of the response
|
||||
*/
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtype of the response.
|
||||
*
|
||||
* @param subtype The subtype of the response
|
||||
*/
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model that was set.
|
||||
*
|
||||
* @return The model that was set
|
||||
*/
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model that was set.
|
||||
*
|
||||
* @param model The model that was set
|
||||
*/
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a control request to set the permission mode in the CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "subtype", typeName = "set_permission_mode")
|
||||
public class CLIControlSetPermissionModeRequest extends ControlRequestPayload {
|
||||
public CLIControlSetPermissionModeRequest() {
|
||||
super();
|
||||
setSubtype("set_permission_mode");
|
||||
}
|
||||
|
||||
/**
|
||||
* The permission mode to set.
|
||||
*/
|
||||
String mode;
|
||||
|
||||
/**
|
||||
* Gets the permission mode to set.
|
||||
*
|
||||
* @return The permission mode to set
|
||||
*/
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the permission mode to set.
|
||||
*
|
||||
* @param mode The permission mode to set
|
||||
*/
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a payload request in the CLI control message.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "subtype", typeName = "ControlRequestPayload",
|
||||
seeAlso = {CLIControlInitializeRequest.class, CLIControlInterruptRequest.class, CLIControlPermissionRequest.class, CLIControlSetModelRequest.class, CLIControlSetPermissionModeRequest.class})
|
||||
public class ControlRequestPayload {
|
||||
/**
|
||||
* The subtype of the request.
|
||||
*/
|
||||
protected String subtype;
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control.payload;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
/**
|
||||
* Represents a payload request in the CLI control message.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
@JSONType(typeKey = "subtype", typeName = "ControlResponsePayload",
|
||||
seeAlso = {CLIControlInitializeResponse.class, CLIControlPermissionResponse.class})
|
||||
public class ControlResponsePayload {
|
||||
/**
|
||||
* The subtype of the request.
|
||||
*/
|
||||
protected String subtype;
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
export interface Annotation {
|
||||
type: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Usage {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
cache_creation_input_tokens?: number;
|
||||
cache_read_input_tokens?: number;
|
||||
total_tokens?: number;
|
||||
}
|
||||
|
||||
export interface ExtendedUsage extends Usage {
|
||||
server_tool_use?: {
|
||||
web_search_requests: number;
|
||||
};
|
||||
service_tier?: string;
|
||||
cache_creation?: {
|
||||
ephemeral_1h_input_tokens: number;
|
||||
ephemeral_5m_input_tokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ModelUsage {
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
cacheReadInputTokens: number;
|
||||
cacheCreationInputTokens: number;
|
||||
webSearchRequests: number;
|
||||
contextWindow: number;
|
||||
}
|
||||
|
||||
export interface CLIPermissionDenial {
|
||||
tool_name: string;
|
||||
tool_use_id: string;
|
||||
tool_input: unknown;
|
||||
}
|
||||
|
||||
export interface TextBlock {
|
||||
type: 'text';
|
||||
text: string;
|
||||
annotations?: Annotation[];
|
||||
}
|
||||
|
||||
export interface ThinkingBlock {
|
||||
type: 'thinking';
|
||||
thinking: string;
|
||||
signature?: string;
|
||||
annotations?: Annotation[];
|
||||
}
|
||||
|
||||
export interface ToolUseBlock {
|
||||
type: 'tool_use';
|
||||
id: string;
|
||||
name: string;
|
||||
input: unknown;
|
||||
annotations?: Annotation[];
|
||||
}
|
||||
|
||||
export interface ToolResultBlock {
|
||||
type: 'tool_result';
|
||||
tool_use_id: string;
|
||||
content?: string | ContentBlock[];
|
||||
is_error?: boolean;
|
||||
annotations?: Annotation[];
|
||||
}
|
||||
|
||||
export type ContentBlock =
|
||||
| TextBlock
|
||||
| ThinkingBlock
|
||||
| ToolUseBlock
|
||||
| ToolResultBlock;
|
||||
|
||||
export interface APIUserMessage {
|
||||
role: 'user';
|
||||
content: string | ContentBlock[];
|
||||
}
|
||||
|
||||
export interface APIAssistantMessage {
|
||||
id: string;
|
||||
type: 'message';
|
||||
role: 'assistant';
|
||||
model: string;
|
||||
content: ContentBlock[];
|
||||
stop_reason?: string | null;
|
||||
usage: Usage;
|
||||
}
|
||||
|
||||
export interface SDKUserMessage {
|
||||
type: 'user';
|
||||
uuid?: string;
|
||||
session_id: string;
|
||||
message: APIUserMessage;
|
||||
parent_tool_use_id: string | null;
|
||||
options?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SDKAssistantMessage {
|
||||
type: 'assistant';
|
||||
uuid: string;
|
||||
session_id: string;
|
||||
message: APIAssistantMessage;
|
||||
parent_tool_use_id: string | null;
|
||||
}
|
||||
|
||||
export interface SDKSystemMessage {
|
||||
type: 'system';
|
||||
subtype: string;
|
||||
uuid: string;
|
||||
session_id: string;
|
||||
data?: unknown;
|
||||
cwd?: string;
|
||||
tools?: string[];
|
||||
mcp_servers?: Array<{
|
||||
name: string;
|
||||
status: string;
|
||||
}>;
|
||||
model?: string;
|
||||
permission_mode?: string;
|
||||
slash_commands?: string[];
|
||||
qwen_code_version?: string;
|
||||
output_style?: string;
|
||||
agents?: string[];
|
||||
skills?: string[];
|
||||
capabilities?: Record<string, unknown>;
|
||||
compact_metadata?: {
|
||||
trigger: 'manual' | 'auto';
|
||||
pre_tokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SDKResultMessageSuccess {
|
||||
type: 'result';
|
||||
subtype: 'success';
|
||||
uuid: string;
|
||||
session_id: string;
|
||||
is_error: false;
|
||||
duration_ms: number;
|
||||
duration_api_ms: number;
|
||||
num_turns: number;
|
||||
result: string;
|
||||
usage: ExtendedUsage;
|
||||
modelUsage?: Record<string, ModelUsage>;
|
||||
permission_denials: CLIPermissionDenial[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface SDKResultMessageError {
|
||||
type: 'result';
|
||||
subtype: 'error_max_turns' | 'error_during_execution';
|
||||
uuid: string;
|
||||
session_id: string;
|
||||
is_error: true;
|
||||
duration_ms: number;
|
||||
duration_api_ms: number;
|
||||
num_turns: number;
|
||||
usage: ExtendedUsage;
|
||||
modelUsage?: Record<string, ModelUsage>;
|
||||
permission_denials: CLIPermissionDenial[];
|
||||
error?: {
|
||||
type?: string;
|
||||
message: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export type SDKResultMessage = SDKResultMessageSuccess | SDKResultMessageError;
|
||||
|
||||
export interface MessageStartStreamEvent {
|
||||
type: 'message_start';
|
||||
message: {
|
||||
id: string;
|
||||
role: 'assistant';
|
||||
model: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContentBlockStartEvent {
|
||||
type: 'content_block_start';
|
||||
index: number;
|
||||
content_block: ContentBlock;
|
||||
}
|
||||
|
||||
export type ContentBlockDelta =
|
||||
| {
|
||||
type: 'text_delta';
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
type: 'thinking_delta';
|
||||
thinking: string;
|
||||
}
|
||||
| {
|
||||
type: 'input_json_delta';
|
||||
partial_json: string;
|
||||
};
|
||||
|
||||
export interface ContentBlockDeltaEvent {
|
||||
type: 'content_block_delta';
|
||||
index: number;
|
||||
delta: ContentBlockDelta;
|
||||
}
|
||||
|
||||
export interface ContentBlockStopEvent {
|
||||
type: 'content_block_stop';
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface MessageStopStreamEvent {
|
||||
type: 'message_stop';
|
||||
}
|
||||
|
||||
export type StreamEvent =
|
||||
| MessageStartStreamEvent
|
||||
| ContentBlockStartEvent
|
||||
| ContentBlockDeltaEvent
|
||||
| ContentBlockStopEvent
|
||||
| MessageStopStreamEvent;
|
||||
|
||||
export interface SDKPartialAssistantMessage {
|
||||
type: 'stream_event';
|
||||
uuid: string;
|
||||
session_id: string;
|
||||
event: StreamEvent;
|
||||
parent_tool_use_id: string | null;
|
||||
}
|
||||
|
||||
export type PermissionMode = 'default' | 'plan' | 'auto-edit' | 'yolo';
|
||||
|
||||
/**
|
||||
* TODO: Align with `ToolCallConfirmationDetails`
|
||||
*/
|
||||
export interface PermissionSuggestion {
|
||||
type: 'allow' | 'deny' | 'modify';
|
||||
label: string;
|
||||
description?: string;
|
||||
modifiedInput?: unknown;
|
||||
}
|
||||
|
||||
export interface HookRegistration {
|
||||
event: string;
|
||||
callback_id: string;
|
||||
}
|
||||
|
||||
export interface HookCallbackResult {
|
||||
shouldSkip?: boolean;
|
||||
shouldInterrupt?: boolean;
|
||||
suppressOutput?: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface CLIControlInterruptRequest {
|
||||
subtype: 'interrupt';
|
||||
}
|
||||
|
||||
export interface CLIControlPermissionRequest {
|
||||
subtype: 'can_use_tool';
|
||||
tool_name: string;
|
||||
tool_use_id: string;
|
||||
input: unknown;
|
||||
permission_suggestions: PermissionSuggestion[] | null;
|
||||
blocked_path: string | null;
|
||||
}
|
||||
|
||||
export enum AuthProviderType {
|
||||
DYNAMIC_DISCOVERY = 'dynamic_discovery',
|
||||
GOOGLE_CREDENTIALS = 'google_credentials',
|
||||
SERVICE_ACCOUNT_IMPERSONATION = 'service_account_impersonation',
|
||||
}
|
||||
|
||||
export interface MCPServerConfig {
|
||||
command?: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
cwd?: string;
|
||||
url?: string;
|
||||
httpUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
tcp?: string;
|
||||
timeout?: number;
|
||||
trust?: boolean;
|
||||
description?: string;
|
||||
includeTools?: string[];
|
||||
excludeTools?: string[];
|
||||
extensionName?: string;
|
||||
oauth?: Record<string, unknown>;
|
||||
authProviderType?: AuthProviderType;
|
||||
targetAudience?: string;
|
||||
targetServiceAccount?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK MCP Server configuration
|
||||
*
|
||||
* SDK MCP servers run in the SDK process and are connected via in-memory transport.
|
||||
* Tool calls are routed through the control plane between SDK and CLI.
|
||||
*/
|
||||
export interface SDKMcpServerConfig {
|
||||
/**
|
||||
* Type identifier for SDK MCP servers
|
||||
*/
|
||||
type: 'sdk';
|
||||
/**
|
||||
* Server name for identification and routing
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The MCP Server instance created by createSdkMcpServer()
|
||||
*/
|
||||
instance: McpServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire format for SDK MCP servers sent to the CLI
|
||||
*/
|
||||
export type WireSDKMcpServerConfig = Omit<SDKMcpServerConfig, 'instance'>;
|
||||
|
||||
export interface CLIControlInitializeRequest {
|
||||
subtype: 'initialize';
|
||||
hooks?: HookRegistration[] | null;
|
||||
/**
|
||||
* SDK MCP servers config
|
||||
* These are MCP servers running in the SDK process, connected via control plane.
|
||||
* External MCP servers are configured separately in settings, not via initialization.
|
||||
*/
|
||||
sdkMcpServers?: Record<string, WireSDKMcpServerConfig>;
|
||||
/**
|
||||
* External MCP servers that should be managed by the CLI.
|
||||
*/
|
||||
mcpServers?: Record<string, MCPServerConfig>;
|
||||
agents?: SubagentConfig[];
|
||||
}
|
||||
|
||||
export interface CLIControlSetPermissionModeRequest {
|
||||
subtype: 'set_permission_mode';
|
||||
mode: PermissionMode;
|
||||
}
|
||||
|
||||
export interface CLIHookCallbackRequest {
|
||||
subtype: 'hook_callback';
|
||||
callback_id: string;
|
||||
input: unknown;
|
||||
tool_use_id: string | null;
|
||||
}
|
||||
|
||||
export interface CLIControlMcpMessageRequest {
|
||||
subtype: 'mcp_message';
|
||||
server_name: string;
|
||||
message: {
|
||||
jsonrpc?: string;
|
||||
method: string;
|
||||
params?: Record<string, unknown>;
|
||||
id?: string | number | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CLIControlSetModelRequest {
|
||||
subtype: 'set_model';
|
||||
model: string;
|
||||
}
|
||||
|
||||
export interface CLIControlMcpStatusRequest {
|
||||
subtype: 'mcp_server_status';
|
||||
}
|
||||
|
||||
export interface CLIControlSupportedCommandsRequest {
|
||||
subtype: 'supported_commands';
|
||||
}
|
||||
|
||||
export type ControlRequestPayload =
|
||||
| CLIControlInterruptRequest
|
||||
| CLIControlPermissionRequest
|
||||
| CLIControlInitializeRequest
|
||||
| CLIControlSetPermissionModeRequest
|
||||
| CLIHookCallbackRequest
|
||||
| CLIControlMcpMessageRequest
|
||||
| CLIControlSetModelRequest
|
||||
| CLIControlMcpStatusRequest
|
||||
| CLIControlSupportedCommandsRequest;
|
||||
|
||||
export interface CLIControlRequest {
|
||||
type: 'control_request';
|
||||
request_id: string;
|
||||
request: ControlRequestPayload;
|
||||
}
|
||||
|
||||
export interface PermissionApproval {
|
||||
allowed: boolean;
|
||||
reason?: string;
|
||||
modifiedInput?: unknown;
|
||||
}
|
||||
|
||||
export interface ControlResponse {
|
||||
subtype: 'success';
|
||||
request_id: string;
|
||||
response: unknown;
|
||||
}
|
||||
|
||||
export interface ControlErrorResponse {
|
||||
subtype: 'error';
|
||||
request_id: string;
|
||||
error: string | { message: string; [key: string]: unknown };
|
||||
}
|
||||
|
||||
export interface CLIControlResponse {
|
||||
type: 'control_response';
|
||||
response: ControlResponse | ControlErrorResponse;
|
||||
}
|
||||
|
||||
export interface ControlCancelRequest {
|
||||
type: 'control_cancel_request';
|
||||
request_id?: string;
|
||||
}
|
||||
|
||||
export type ControlMessage =
|
||||
| CLIControlRequest
|
||||
| CLIControlResponse
|
||||
| ControlCancelRequest;
|
||||
|
||||
/**
|
||||
* Union of all SDK message types
|
||||
*/
|
||||
export type SDKMessage =
|
||||
| SDKUserMessage
|
||||
| SDKAssistantMessage
|
||||
| SDKSystemMessage
|
||||
| SDKResultMessage
|
||||
| SDKPartialAssistantMessage;
|
||||
|
||||
export function isSDKUserMessage(msg: any): msg is SDKUserMessage {
|
||||
return (
|
||||
msg && typeof msg === 'object' && msg.type === 'user' && 'message' in msg
|
||||
);
|
||||
}
|
||||
|
||||
export function isSDKAssistantMessage(msg: any): msg is SDKAssistantMessage {
|
||||
return (
|
||||
msg &&
|
||||
typeof msg === 'object' &&
|
||||
msg.type === 'assistant' &&
|
||||
'uuid' in msg &&
|
||||
'message' in msg &&
|
||||
'session_id' in msg &&
|
||||
'parent_tool_use_id' in msg
|
||||
);
|
||||
}
|
||||
|
||||
export function isSDKSystemMessage(msg: any): msg is SDKSystemMessage {
|
||||
return (
|
||||
msg &&
|
||||
typeof msg === 'object' &&
|
||||
msg.type === 'system' &&
|
||||
'subtype' in msg &&
|
||||
'uuid' in msg &&
|
||||
'session_id' in msg
|
||||
);
|
||||
}
|
||||
|
||||
export function isSDKResultMessage(msg: any): msg is SDKResultMessage {
|
||||
return (
|
||||
msg &&
|
||||
typeof msg === 'object' &&
|
||||
msg.type === 'result' &&
|
||||
'subtype' in msg &&
|
||||
'duration_ms' in msg &&
|
||||
'is_error' in msg &&
|
||||
'uuid' in msg &&
|
||||
'session_id' in msg
|
||||
);
|
||||
}
|
||||
|
||||
export function isSDKPartialAssistantMessage(
|
||||
msg: any,
|
||||
): msg is SDKPartialAssistantMessage {
|
||||
return (
|
||||
msg &&
|
||||
typeof msg === 'object' &&
|
||||
msg.type === 'stream_event' &&
|
||||
'uuid' in msg &&
|
||||
'session_id' in msg &&
|
||||
'event' in msg &&
|
||||
'parent_tool_use_id' in msg
|
||||
);
|
||||
}
|
||||
|
||||
export function isControlRequest(msg: any): msg is CLIControlRequest {
|
||||
return (
|
||||
msg &&
|
||||
typeof msg === 'object' &&
|
||||
msg.type === 'control_request' &&
|
||||
'request_id' in msg &&
|
||||
'request' in msg
|
||||
);
|
||||
}
|
||||
|
||||
export function isControlResponse(msg: any): msg is CLIControlResponse {
|
||||
return (
|
||||
msg &&
|
||||
typeof msg === 'object' &&
|
||||
msg.type === 'control_response' &&
|
||||
'response' in msg
|
||||
);
|
||||
}
|
||||
|
||||
export function isControlCancel(msg: any): msg is ControlCancelRequest {
|
||||
return (
|
||||
msg &&
|
||||
typeof msg === 'object' &&
|
||||
msg.type === 'control_cancel_request' &&
|
||||
'request_id' in msg
|
||||
);
|
||||
}
|
||||
|
||||
export function isTextBlock(block: any): block is TextBlock {
|
||||
return block && typeof block === 'object' && block.type === 'text';
|
||||
}
|
||||
|
||||
export function isThinkingBlock(block: any): block is ThinkingBlock {
|
||||
return block && typeof block === 'object' && block.type === 'thinking';
|
||||
}
|
||||
|
||||
export function isToolUseBlock(block: any): block is ToolUseBlock {
|
||||
return block && typeof block === 'object' && block.type === 'tool_use';
|
||||
}
|
||||
|
||||
export function isToolResultBlock(block: any): block is ToolResultBlock {
|
||||
return block && typeof block === 'object' && block.type === 'tool_result';
|
||||
}
|
||||
|
||||
export type SubagentLevel = 'session';
|
||||
|
||||
export interface ModelConfig {
|
||||
model?: string;
|
||||
temp?: number;
|
||||
top_p?: number;
|
||||
}
|
||||
|
||||
export interface RunConfig {
|
||||
max_time_minutes?: number;
|
||||
max_turns?: number;
|
||||
}
|
||||
|
||||
export interface SubagentConfig {
|
||||
name: string;
|
||||
description: string;
|
||||
tools?: string[];
|
||||
systemPrompt: string;
|
||||
level: SubagentLevel;
|
||||
filePath?: string;
|
||||
modelConfig?: Partial<ModelConfig>;
|
||||
runConfig?: Partial<RunConfig>;
|
||||
color?: string;
|
||||
readonly isBuiltin?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Control Request Types
|
||||
*
|
||||
* Centralized enum for all control request subtypes supported by the CLI.
|
||||
* This enum should be kept in sync with the controllers in:
|
||||
* - packages/cli/src/services/control/controllers/systemController.ts
|
||||
* - packages/cli/src/services/control/controllers/permissionController.ts
|
||||
* - packages/cli/src/services/control/controllers/mcpController.ts
|
||||
* - packages/cli/src/services/control/controllers/hookController.ts
|
||||
*/
|
||||
export enum ControlRequestType {
|
||||
// SystemController requests
|
||||
INITIALIZE = 'initialize',
|
||||
INTERRUPT = 'interrupt',
|
||||
SET_MODEL = 'set_model',
|
||||
SUPPORTED_COMMANDS = 'supported_commands',
|
||||
|
||||
// PermissionController requests
|
||||
CAN_USE_TOOL = 'can_use_tool',
|
||||
SET_PERMISSION_MODE = 'set_permission_mode',
|
||||
|
||||
// MCPController requests
|
||||
MCP_MESSAGE = 'mcp_message',
|
||||
MCP_SERVER_STATUS = 'mcp_server_status',
|
||||
|
||||
// HookController requests
|
||||
HOOK_CALLBACK = 'hook_callback',
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
package com.alibaba.qwen.code.cli.session;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.JSONReader.Feature;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.Capabilities;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKPartialAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlInitializeRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlInitializeResponse;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlInterruptRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlSetModelRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlSetPermissionModeRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.exception.SessionControlException;
|
||||
import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException;
|
||||
import com.alibaba.qwen.code.cli.transport.Transport;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.utils.MyConcurrentUtils;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manages a session with the Qwen Code CLI, handling communication, sending prompts, and processing responses.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class Session {
|
||||
private static final Logger log = LoggerFactory.getLogger(Session.class);
|
||||
private final Transport transport;
|
||||
private CLIControlInitializeResponse lastCliControlInitializeResponse;
|
||||
private SDKSystemMessage lastSdkSystemMessage;
|
||||
private final Timeout defaultEventTimeout = Timeout.TIMEOUT_60_SECONDS;
|
||||
|
||||
/**
|
||||
* Checks if the session is configured for streaming.
|
||||
*
|
||||
* @return true if streaming is enabled, false otherwise
|
||||
*/
|
||||
public boolean isStreaming() {
|
||||
return Optional.ofNullable(transport)
|
||||
.map(Transport::getTransportOptions)
|
||||
.map(TransportOptions::getIncludePartialMessages)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new session with the specified transport.
|
||||
*
|
||||
* @param transport The transport layer to use for communication
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the transport is not available
|
||||
*/
|
||||
public Session(Transport transport) throws SessionControlException {
|
||||
if (transport == null || !transport.isAvailable()) {
|
||||
throw new SessionControlException("Transport is not available");
|
||||
}
|
||||
this.transport = transport;
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the session by initializing communication with the CLI.
|
||||
*
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if initialization fails
|
||||
*/
|
||||
public void start() throws SessionControlException {
|
||||
try {
|
||||
if (!transport.isAvailable()) {
|
||||
transport.start();
|
||||
}
|
||||
String response = transport.inputWaitForOneLine(CLIControlRequest.create(new CLIControlInitializeRequest()).toString());
|
||||
CLIControlResponse<CLIControlInitializeResponse> cliControlResponse = JSON.parseObject(response,
|
||||
new TypeReference<CLIControlResponse<CLIControlInitializeResponse>>() {});
|
||||
this.lastCliControlInitializeResponse = cliControlResponse.getResponse().getResponse();
|
||||
} catch (Exception e) {
|
||||
throw new SessionControlException("Failed to initialize the session", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the session and releases resources.
|
||||
*
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if closing fails
|
||||
*/
|
||||
public void close() throws SessionControlException {
|
||||
try {
|
||||
transport.close();
|
||||
} catch (Exception e) {
|
||||
throw new SessionControlException("Failed to close the session", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrupts the current operation in the CLI.
|
||||
*
|
||||
* @return An optional boolean indicating success of the interrupt operation
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails
|
||||
*/
|
||||
public Optional<Boolean> interrupt() throws SessionControlException {
|
||||
checkAvailable();
|
||||
return processControlRequest(new CLIControlRequest<CLIControlInterruptRequest>().setRequest(new CLIControlInterruptRequest()).toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model to be used in the session.
|
||||
*
|
||||
* @param modelName The name of the model to use
|
||||
* @return An optional boolean indicating success of the operation
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails
|
||||
*/
|
||||
public Optional<Boolean> setModel(String modelName) throws SessionControlException {
|
||||
checkAvailable();
|
||||
CLIControlSetModelRequest cliControlSetModelRequest = new CLIControlSetModelRequest();
|
||||
cliControlSetModelRequest.setModel(modelName);
|
||||
return processControlRequest(new CLIControlRequest<CLIControlSetModelRequest>().setRequest(cliControlSetModelRequest).toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the permission mode for the session.
|
||||
*
|
||||
* @param permissionMode The permission mode to use
|
||||
* @return An optional boolean indicating success of the operation
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails
|
||||
*/
|
||||
public Optional<Boolean> setPermissionMode(PermissionMode permissionMode) throws SessionControlException {
|
||||
checkAvailable();
|
||||
CLIControlSetPermissionModeRequest cliControlSetPermissionModeRequest = new CLIControlSetPermissionModeRequest();
|
||||
cliControlSetPermissionModeRequest.setMode(permissionMode.getValue());
|
||||
return processControlRequest(
|
||||
new CLIControlRequest<CLIControlSetPermissionModeRequest>().setRequest(cliControlSetPermissionModeRequest).toString());
|
||||
}
|
||||
|
||||
private Optional<Boolean> processControlRequest(String request) throws SessionControlException {
|
||||
try {
|
||||
if (transport.isReading()) {
|
||||
transport.inputNoWaitResponse(request);
|
||||
return Optional.empty();
|
||||
} else {
|
||||
String response = transport.inputWaitForOneLine(request);
|
||||
CLIControlResponse<?> cliControlResponse = JSON.parseObject(response, new TypeReference<CLIControlResponse<?>>() {});
|
||||
return Optional.of("success".equals(cliControlResponse.getResponse().getSubtype()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SessionControlException("Failed to set model", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Continues the current session.
|
||||
*
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails
|
||||
*/
|
||||
public void continueSession() throws SessionControlException {
|
||||
resumeSession(getSessionId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes a session with the specified ID.
|
||||
*
|
||||
* @param sessionId The ID of the session to resume
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails
|
||||
*/
|
||||
public void resumeSession(String sessionId) throws SessionControlException {
|
||||
if (StringUtils.isNotBlank(sessionId)) {
|
||||
transport.getTransportOptions().setResumeSessionId(sessionId);
|
||||
}
|
||||
this.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a prompt to the CLI and processes the response.
|
||||
*
|
||||
* @param prompt The prompt to send to the CLI
|
||||
* @param sessionEventConsumers Consumers for handling different types of events
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException if sending the prompt fails
|
||||
* @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if a control operation fails
|
||||
*/
|
||||
public void sendPrompt(String prompt, SessionEventConsumers sessionEventConsumers) throws SessionSendPromptException, SessionControlException {
|
||||
checkAvailable();
|
||||
try {
|
||||
transport.inputWaitForMultiLine(new SDKUserMessage().setContent(prompt).toString(), (line) -> {
|
||||
JSONObject jsonObject = JSON.parseObject(line);
|
||||
String messageType = jsonObject.getString("type");
|
||||
if ("system".equals(messageType)) {
|
||||
lastSdkSystemMessage = jsonObject.to(SDKSystemMessage.class);
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onSystemMessage(this, lastSdkSystemMessage),
|
||||
Optional.ofNullable(sessionEventConsumers.onSystemMessageTimeout(this, lastSdkSystemMessage))
|
||||
.orElse(defaultEventTimeout));
|
||||
return false;
|
||||
} else if ("assistant".equals(messageType)) {
|
||||
SDKAssistantMessage assistantMessage = jsonObject.to(SDKAssistantMessage.class);
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onAssistantMessage(this, assistantMessage),
|
||||
Optional.ofNullable(sessionEventConsumers.onAssistantMessageTimeout(this, assistantMessage)).orElse(defaultEventTimeout));
|
||||
return false;
|
||||
} else if ("stream_event".equals(messageType)) {
|
||||
SDKPartialAssistantMessage sdkPartialAssistantMessage = jsonObject.to(SDKPartialAssistantMessage.class);
|
||||
MyConcurrentUtils.runAndWait(
|
||||
() -> sessionEventConsumers.onPartialAssistantMessage(this, sdkPartialAssistantMessage),
|
||||
Optional.ofNullable(sessionEventConsumers.onPartialAssistantMessageTimeout(this, sdkPartialAssistantMessage))
|
||||
.orElse(defaultEventTimeout));
|
||||
return false;
|
||||
} else if ("user".equals(messageType)) {
|
||||
SDKUserMessage sdkUserMessage = jsonObject.to(SDKUserMessage.class, Feature.FieldBased);
|
||||
MyConcurrentUtils.runAndWait(
|
||||
() -> sessionEventConsumers.onUserMessage(this, sdkUserMessage),
|
||||
Optional.ofNullable(sessionEventConsumers.onUserMessageTimeout(this, sdkUserMessage)).orElse(defaultEventTimeout));
|
||||
return false;
|
||||
} else if ("result".equals(messageType)) {
|
||||
SDKResultMessage sdkResultMessage = jsonObject.to(SDKResultMessage.class);
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onResultMessage(this, sdkResultMessage),
|
||||
Optional.ofNullable(sessionEventConsumers.onResultMessageTimeout(this, sdkResultMessage)).orElse(defaultEventTimeout));
|
||||
return true;
|
||||
} else if ("control_response".equals(messageType)) {
|
||||
CLIControlResponse<? extends ControlResponsePayload> controlResponse = jsonObject.to(
|
||||
new TypeReference<CLIControlResponse<? extends ControlResponsePayload>>() {});
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onControlResponse(this, controlResponse),
|
||||
Optional.ofNullable(sessionEventConsumers.onControlResponseTimeout(this, controlResponse)).orElse(defaultEventTimeout));
|
||||
if (!"error".equals(jsonObject.getString("subtype"))) {
|
||||
return false;
|
||||
} else {
|
||||
log.info("control_response error: {}", jsonObject.toJSONString());
|
||||
return "error".equals(jsonObject.getString("subtype"));
|
||||
}
|
||||
} else if ("control_request".equals(messageType)) {
|
||||
CLIControlResponse<? extends ControlResponsePayload> controlResponse;
|
||||
try {
|
||||
CLIControlRequest<? extends ControlRequestPayload> controlRequest = jsonObject.to(
|
||||
new TypeReference<CLIControlRequest<? extends ControlRequestPayload>>() {});
|
||||
controlResponse = MyConcurrentUtils.runAndWait(
|
||||
() -> sessionEventConsumers.onControlRequest(this, controlRequest),
|
||||
Optional.ofNullable(sessionEventConsumers.onControlRequestTimeout(this, controlRequest)).orElse(defaultEventTimeout));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to process control request", e);
|
||||
controlResponse = new CLIControlResponse<>();
|
||||
}
|
||||
try {
|
||||
transport.inputNoWaitResponse(Optional.ofNullable(controlResponse).map(CLIControlResponse::toString)
|
||||
.orElse(new CLIControlResponse<ControlResponsePayload>().toString()));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to send control response", e);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
log.warn("unknown message type: {}", messageType);
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onOtherMessage(this, line),
|
||||
Optional.ofNullable(sessionEventConsumers.onOtherMessageTimeout(this, line)).orElse(defaultEventTimeout));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new SessionSendPromptException("Failed to send prompt", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current session ID.
|
||||
*
|
||||
* @return The session ID, or null if not available
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return Optional.ofNullable(lastSdkSystemMessage).map(SDKSystemMessage::getSessionId).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the session is available for operations.
|
||||
*
|
||||
* @return true if the session is available, false otherwise
|
||||
*/
|
||||
public boolean isAvailable() {
|
||||
return transport.isAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the capabilities of the CLI.
|
||||
*
|
||||
* @return A Capabilities object representing the CLI's capabilities
|
||||
*/
|
||||
public Capabilities getCapabilities() {
|
||||
return Optional.ofNullable(lastCliControlInitializeResponse).map(CLIControlInitializeResponse::getCapabilities).orElse(new Capabilities());
|
||||
}
|
||||
|
||||
private void checkAvailable() throws SessionControlException {
|
||||
if (!isAvailable()) {
|
||||
throw new SessionControlException("Session is not available");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.alibaba.qwen.code.cli.session.event.consumers;
|
||||
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlPermissionRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
/**
|
||||
* Interface for handling different types of assistant content during a session.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public interface AssistantContentConsumers {
|
||||
/**
|
||||
* Handles text content from the assistant.
|
||||
*
|
||||
* @param session The session
|
||||
* @param textAssistantContent The text content from the assistant
|
||||
*/
|
||||
void onText(Session session, TextAssistantContent textAssistantContent);
|
||||
|
||||
/**
|
||||
* Handles thinking content from the assistant.
|
||||
*
|
||||
* @param session The session
|
||||
* @param thingkingAssistantContent The thinking content from the assistant
|
||||
*/
|
||||
void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent);
|
||||
|
||||
/**
|
||||
* Handles tool use content from the assistant.
|
||||
*
|
||||
* @param session The session
|
||||
* @param toolUseAssistantContent The tool use content from the assistant
|
||||
*/
|
||||
void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent);
|
||||
|
||||
/**
|
||||
* Handles tool result content from the assistant.
|
||||
*
|
||||
* @param session The session
|
||||
* @param toolResultAssistantContent The tool result content from the assistant
|
||||
*/
|
||||
void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent);
|
||||
|
||||
/**
|
||||
* Handles other types of assistant content.
|
||||
*
|
||||
* @param session The session
|
||||
* @param other The other content from the assistant
|
||||
*/
|
||||
void onOtherContent(Session session, AssistantContent<?> other);
|
||||
|
||||
/**
|
||||
* Handles permission requests.
|
||||
*
|
||||
* @param session The session
|
||||
* @param permissionRequest The permission request
|
||||
* @return The behavior for the permission request
|
||||
*/
|
||||
Behavior onPermissionRequest(Session session, CLIControlPermissionRequest permissionRequest);
|
||||
|
||||
/**
|
||||
* Handles permission requests.
|
||||
*
|
||||
* @param session The session
|
||||
* @param requestPayload The control request payload
|
||||
* @return The response payload for the control request
|
||||
*/
|
||||
ControlResponsePayload onOtherControlRequest(Session session, ControlRequestPayload requestPayload);
|
||||
|
||||
/**
|
||||
* Handles usage information from the assistant.
|
||||
*
|
||||
* @param session The session
|
||||
* @param AssistantUsage The usage information from the assistant
|
||||
*/
|
||||
void onUsage(Session session, AssistantUsage AssistantUsage);
|
||||
|
||||
/**
|
||||
* Sets the default permission operation.
|
||||
*
|
||||
* @param defaultPermissionOperation The default permission operation
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
AssistantContentSimpleConsumers setDefaultPermissionOperation(Operation defaultPermissionOperation);
|
||||
|
||||
/**
|
||||
* Gets timeout for permission request handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @return The timeout for permission request handling
|
||||
*/
|
||||
Timeout onPermissionRequestTimeout(Session session, CLIControlPermissionRequest permissionRequest);
|
||||
|
||||
/**
|
||||
* Gets timeout for other control request handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param requestPayload The control request payload
|
||||
* @return The timeout for other control request handling
|
||||
*/
|
||||
Timeout onOtherControlRequestTimeout(Session session, ControlRequestPayload requestPayload);
|
||||
|
||||
/**
|
||||
* Gets timeout for text handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param textAssistantContent The text content from the assistant
|
||||
* @return The timeout for text handling
|
||||
*/
|
||||
Timeout onTextTimeout(Session session, TextAssistantContent textAssistantContent);
|
||||
|
||||
/**
|
||||
* Gets timeout for thinking handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param thingkingAssistantContent The thinking content from the assistant
|
||||
* @return The timeout for thinking handling
|
||||
*/
|
||||
Timeout onThinkingTimeout(Session session, ThingkingAssistantContent thingkingAssistantContent);
|
||||
|
||||
/**
|
||||
* Gets timeout for tool use handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param toolUseAssistantContent The tool use content from the assistant
|
||||
* @return The timeout for tool use handling
|
||||
*/
|
||||
Timeout onToolUseTimeout(Session session, ToolUseAssistantContent toolUseAssistantContent);
|
||||
|
||||
/**
|
||||
* Gets timeout for tool result handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param toolResultAssistantContent The tool result content from the assistant
|
||||
* @return The timeout for tool result handling
|
||||
*/
|
||||
Timeout onToolResultTimeout(Session session, ToolResultAssistantContent toolResultAssistantContent);
|
||||
|
||||
/**
|
||||
* Gets timeout for other content handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param other The other content from the assistant
|
||||
* @return The timeout for other content handling
|
||||
*/
|
||||
Timeout onOtherContentTimeout(Session session, AssistantContent<?> other);
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.alibaba.qwen.code.cli.session.event.consumers;
|
||||
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Deny;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlPermissionRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Simple implementation of AssistantContentConsumers that provides empty implementations for all methods.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class AssistantContentSimpleConsumers implements AssistantContentConsumers {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onText(Session session, TextAssistantContent textAssistantContent) {
|
||||
log.debug("Received textAssistantContent {}", textAssistantContent.getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) {
|
||||
log.debug("Received thingkingAssistantContent {}", thingkingAssistantContent.getThinking());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) {
|
||||
log.debug("Received toolUseAssistantContent {}", toolUseAssistantContent.getInput());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Received toolResultAssistantContent {}", toolResultAssistantContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onOtherContent(Session session, AssistantContent<?> other) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Received other content {}", other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Behavior onPermissionRequest(Session session, CLIControlPermissionRequest permissionRequest) {
|
||||
if (Operation.deny.equals(this.defaultPermissionOperation)) {
|
||||
log.info("use defaultPermissionOperation Permission denied.");
|
||||
return new Deny().setMessage("Permission denied.");
|
||||
} else {
|
||||
log.info("use defaultPermissionOperation Permission allowed.");
|
||||
return new Allow().setUpdatedInput(permissionRequest.getInput());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlResponsePayload onOtherControlRequest(Session session, ControlRequestPayload requestPayload) {
|
||||
throw new RuntimeException("need override onOtherControlRequest");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onUsage(Session session, AssistantUsage AssistantUsage) {
|
||||
log.info("received usage {} of message {}", AssistantUsage.getUsage(), AssistantUsage.getMessageId());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onPermissionRequestTimeout(Session session, CLIControlPermissionRequest permissionRequest) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onOtherControlRequestTimeout(Session session, ControlRequestPayload requestPayload) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onTextTimeout(Session session, TextAssistantContent textAssistantContent) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onThinkingTimeout(Session session, ThingkingAssistantContent thingkingAssistantContent) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onToolUseTimeout(Session session, ToolUseAssistantContent toolUseAssistantContent) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onToolResultTimeout(Session session, ToolResultAssistantContent toolResultAssistantContent) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onOtherContentTimeout(Session session, AssistantContent<?> other) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public AssistantContentSimpleConsumers setDefaultPermissionOperation(Operation defaultPermissionOperation) {
|
||||
this.defaultPermissionOperation = defaultPermissionOperation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param defaultPermissionOperation The default permission operation.
|
||||
* @param defaultEventTimeout The default event timeout.
|
||||
*/
|
||||
public AssistantContentSimpleConsumers(Operation defaultPermissionOperation, Timeout defaultEventTimeout) {
|
||||
this.defaultPermissionOperation = defaultPermissionOperation;
|
||||
this.defaultEventTimeout = defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public AssistantContentSimpleConsumers() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The default permission operation.
|
||||
*/
|
||||
private Operation defaultPermissionOperation = Operation.deny;
|
||||
|
||||
/**
|
||||
* The default event timeout.
|
||||
*/
|
||||
protected Timeout defaultEventTimeout = Timeout.TIMEOUT_60_SECONDS;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AssistantContentSimpleConsumers.class);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.alibaba.qwen.code.cli.session.event.consumers;
|
||||
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKPartialAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
/**
|
||||
* Interface for handling different types of events during a session.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public interface SessionEventConsumers {
|
||||
/**
|
||||
* Handles system messages.
|
||||
*
|
||||
* @param session The session
|
||||
* @param systemMessage The system message
|
||||
*/
|
||||
void onSystemMessage(Session session, SDKSystemMessage systemMessage);
|
||||
|
||||
/**
|
||||
* Handles result messages.
|
||||
*
|
||||
* @param session The session
|
||||
* @param resultMessage The result message
|
||||
*/
|
||||
void onResultMessage(Session session, SDKResultMessage resultMessage);
|
||||
|
||||
/**
|
||||
* Handles assistant messages.
|
||||
*
|
||||
* @param session The session
|
||||
* @param assistantMessage The assistant message
|
||||
*/
|
||||
void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage);
|
||||
|
||||
/**
|
||||
* Handles partial assistant messages.
|
||||
*
|
||||
* @param session The session
|
||||
* @param partialAssistantMessage The partial assistant message
|
||||
*/
|
||||
void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage);
|
||||
|
||||
/**
|
||||
* Handles user messages.
|
||||
*
|
||||
* @param session The session
|
||||
* @param userMessage The user message
|
||||
*/
|
||||
void onUserMessage(Session session, SDKUserMessage userMessage);
|
||||
|
||||
/**
|
||||
* Handles other types of messages.
|
||||
*
|
||||
* @param session The session
|
||||
* @param message The message
|
||||
*/
|
||||
void onOtherMessage(Session session, String message);
|
||||
|
||||
/**
|
||||
* Handles control responses.
|
||||
*
|
||||
* @param session The session
|
||||
* @param cliControlResponse The control response
|
||||
*/
|
||||
void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse);
|
||||
|
||||
/**
|
||||
* Handles control requests.
|
||||
*
|
||||
* @param session The session
|
||||
* @param cliControlRequest The control request
|
||||
* @return The control response
|
||||
*/
|
||||
CLIControlResponse<? extends ControlResponsePayload> onControlRequest(Session session, CLIControlRequest<? extends ControlRequestPayload> cliControlRequest);
|
||||
|
||||
/**
|
||||
* Gets timeout for system message handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param systemMessage The system message
|
||||
* @return The timeout for system message handling
|
||||
*/
|
||||
Timeout onSystemMessageTimeout(Session session, SDKSystemMessage systemMessage);
|
||||
|
||||
/**
|
||||
* Gets timeout for result message handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param resultMessage The result message
|
||||
* @return The timeout for result message handling
|
||||
*/
|
||||
Timeout onResultMessageTimeout(Session session, SDKResultMessage resultMessage);
|
||||
|
||||
/**
|
||||
* Gets timeout for assistant message handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param assistantMessage The assistant message
|
||||
* @return The timeout for assistant message handling
|
||||
*/
|
||||
Timeout onAssistantMessageTimeout(Session session, SDKAssistantMessage assistantMessage);
|
||||
|
||||
/**
|
||||
* Gets timeout for partial assistant message handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param partialAssistantMessage The partial assistant message
|
||||
* @return The timeout for partial assistant message handling
|
||||
*/
|
||||
Timeout onPartialAssistantMessageTimeout(Session session, SDKPartialAssistantMessage partialAssistantMessage);
|
||||
|
||||
/**
|
||||
* Gets timeout for user message handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param userMessage The user message
|
||||
* @return The timeout for user message handling
|
||||
*/
|
||||
Timeout onUserMessageTimeout(Session session, SDKUserMessage userMessage);
|
||||
|
||||
/**
|
||||
* Gets timeout for other message handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param message The message
|
||||
* @return The timeout for other message handling
|
||||
*/
|
||||
Timeout onOtherMessageTimeout(Session session, String message);
|
||||
|
||||
/**
|
||||
* Gets timeout for control response handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param cliControlResponse The control response
|
||||
* @return The timeout for control response handling
|
||||
*/
|
||||
Timeout onControlResponseTimeout(Session session, CLIControlResponse<?> cliControlResponse);
|
||||
|
||||
/**
|
||||
* Gets timeout for control request handling.
|
||||
*
|
||||
* @param session The session
|
||||
* @param cliControlRequest The control request
|
||||
* @return The timeout for control request handling
|
||||
*/
|
||||
Timeout onControlRequestTimeout(Session session, CLIControlRequest<?> cliControlRequest);
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
package com.alibaba.qwen.code.cli.session.event.consumers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKPartialAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.event.ContentBlockDeltaEvent;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.event.StreamEvent;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlPermissionRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlPermissionResponse;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.utils.MyConcurrentUtils;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Simple implementation of SessionEventConsumers that provides basic implementations for all methods.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class SessionEventSimpleConsumers implements SessionEventConsumers {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onSystemMessage(Session session, SDKSystemMessage systemMessage) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onResultMessage(Session session, SDKResultMessage resultMessage) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
|
||||
List<ContentBlock<?>> contentBlocks = assistantMessage.getMessage().getContent();
|
||||
if (assistantContentConsumers == null || contentBlocks == null || contentBlocks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
assistantContentConsumers.onUsage(session,
|
||||
new AssistantUsage(assistantMessage.getMessage().getId(), assistantMessage.getMessage().getUsage()));
|
||||
|
||||
if (!session.isStreaming()) {
|
||||
contentBlocks.forEach(contentBlock -> consumeAssistantContent(session, contentBlock));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) {
|
||||
StreamEvent event = partialAssistantMessage.getEvent();
|
||||
if (!(event instanceof ContentBlockDeltaEvent)) {
|
||||
log.debug("received partialAssistantMessage and is not instance of ContentBlockDeltaEvent, will ignore process. the message is {}",
|
||||
partialAssistantMessage);
|
||||
return;
|
||||
}
|
||||
ContentBlockDeltaEvent contentBlockDeltaEvent = (ContentBlockDeltaEvent) event;
|
||||
contentBlockDeltaEvent.getDelta().setMessageId(partialAssistantMessage.getMessageId());
|
||||
consumeAssistantContent(session, contentBlockDeltaEvent.getDelta());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>consumeAssistantContent.</p>
|
||||
*
|
||||
* @param session a {@link com.alibaba.qwen.code.cli.session.Session} object.
|
||||
* @param assistantContent a {@link com.alibaba.qwen.code.cli.protocol.data.AssistantContent} object.
|
||||
*/
|
||||
protected void consumeAssistantContent(Session session, AssistantContent<?> assistantContent) {
|
||||
if (assistantContent instanceof TextAssistantContent) {
|
||||
MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onText(session, (TextAssistantContent) assistantContent),
|
||||
Optional.ofNullable(assistantContentConsumers.onTextTimeout(session, (TextAssistantContent) assistantContent))
|
||||
.orElse(defaultEventTimeout));
|
||||
} else if (assistantContent instanceof ThingkingAssistantContent) {
|
||||
MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onThinking(session, (ThingkingAssistantContent) assistantContent),
|
||||
Optional.ofNullable(assistantContentConsumers.onThinkingTimeout(session, (ThingkingAssistantContent) assistantContent))
|
||||
.orElse(defaultEventTimeout));
|
||||
} else if (assistantContent instanceof ToolUseAssistantContent) {
|
||||
MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onToolUse(session, (ToolUseAssistantContent) assistantContent),
|
||||
Optional.ofNullable(assistantContentConsumers.onToolUseTimeout(session, (ToolUseAssistantContent) assistantContent))
|
||||
.orElse(defaultEventTimeout));
|
||||
} else if (assistantContent instanceof ToolResultAssistantContent) {
|
||||
MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onToolResult(session, (ToolResultAssistantContent) assistantContent),
|
||||
Optional.ofNullable(assistantContentConsumers.onToolResultTimeout(session, (ToolResultAssistantContent) assistantContent))
|
||||
.orElse(defaultEventTimeout));
|
||||
} else {
|
||||
MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onOtherContent(session, assistantContent),
|
||||
Optional.ofNullable(assistantContentConsumers.onOtherContentTimeout(session, assistantContent)).orElse(defaultEventTimeout));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onUserMessage(Session session, SDKUserMessage userMessage) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onOtherMessage(Session session, String message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public CLIControlResponse<? extends ControlResponsePayload> onControlRequest(Session session, CLIControlRequest<?> cliControlRequest) {
|
||||
if (assistantContentConsumers == null) {
|
||||
throw new RuntimeException("please set assistantContentConsumers or override onControlRequest of ");
|
||||
}
|
||||
ControlRequestPayload payload = cliControlRequest.getRequest();
|
||||
if (payload instanceof CLIControlPermissionRequest) {
|
||||
CLIControlPermissionRequest permissionRequest = (CLIControlPermissionRequest) payload;
|
||||
return supplyPermissionControlResponse(session, permissionRequest, cliControlRequest.getRequestId());
|
||||
} else {
|
||||
ControlRequestPayload request = cliControlRequest.getRequest();
|
||||
return supplyOtherControlResponse(session, request, cliControlRequest.getRequestId());
|
||||
}
|
||||
}
|
||||
|
||||
private CLIControlResponse<CLIControlPermissionResponse> supplyPermissionControlResponse(Session session,
|
||||
CLIControlPermissionRequest permissionRequest, String requestId) {
|
||||
Behavior behavior;
|
||||
try {
|
||||
behavior = Optional.ofNullable(
|
||||
MyConcurrentUtils.runAndWait(() -> this.assistantContentConsumers.onPermissionRequest(session, permissionRequest),
|
||||
Optional.ofNullable(assistantContentConsumers.onPermissionRequestTimeout(session, permissionRequest))
|
||||
.orElse(defaultEventTimeout)))
|
||||
.map(b -> {
|
||||
if (b instanceof Allow) {
|
||||
Allow allow = (Allow) b;
|
||||
if (allow.getUpdatedInput() == null) {
|
||||
allow.setUpdatedInput(permissionRequest.getInput());
|
||||
}
|
||||
}
|
||||
return b;
|
||||
})
|
||||
.orElse(Behavior.defaultBehavior());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to process permission response", e);
|
||||
behavior = Behavior.defaultBehavior();
|
||||
}
|
||||
|
||||
CLIControlResponse<CLIControlPermissionResponse> permissionResponse = new CLIControlResponse<>();
|
||||
permissionResponse.createResponse().setResponse(new CLIControlPermissionResponse().setBehavior(behavior)).setRequestId(requestId);
|
||||
return permissionResponse;
|
||||
}
|
||||
|
||||
private CLIControlResponse<ControlResponsePayload> supplyOtherControlResponse(Session session, ControlRequestPayload requestPayload,
|
||||
String requestId) {
|
||||
ControlResponsePayload controlResponsePayload;
|
||||
try {
|
||||
controlResponsePayload = Optional.ofNullable(
|
||||
MyConcurrentUtils.runAndWait(() -> this.assistantContentConsumers.onOtherControlRequest(session, requestPayload),
|
||||
ObjectUtils.getIfNull(assistantContentConsumers.onOtherControlRequestTimeout(session, requestPayload),
|
||||
defaultEventTimeout)))
|
||||
.orElse(new ControlResponsePayload());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to process permission response", e);
|
||||
controlResponsePayload = new ControlResponsePayload();
|
||||
}
|
||||
|
||||
CLIControlResponse<ControlResponsePayload> cliControlResponse = new CLIControlResponse<>();
|
||||
cliControlResponse.createResponse().setResponse(controlResponsePayload).setRequestId(requestId);
|
||||
return cliControlResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onSystemMessageTimeout(Session session, SDKSystemMessage systemMessage) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onResultMessageTimeout(Session session, SDKResultMessage resultMessage) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onAssistantMessageTimeout(Session session, SDKAssistantMessage assistantMessage) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onPartialAssistantMessageTimeout(Session session, SDKPartialAssistantMessage partialAssistantMessage) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onUserMessageTimeout(Session session, SDKUserMessage userMessage) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onOtherMessageTimeout(Session session, String message) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onControlResponseTimeout(Session session, CLIControlResponse<?> cliControlResponse) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Timeout onControlRequestTimeout(Session session, CLIControlRequest<?> cliControlRequest) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default event timeout.
|
||||
*
|
||||
* @return The default event timeout
|
||||
*/
|
||||
protected Timeout getDefaultEventTimeout() {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default event timeout.
|
||||
*
|
||||
* @param defaultEventTimeout The default event timeout
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public SessionEventSimpleConsumers setDefaultEventTimeout(Timeout defaultEventTimeout) {
|
||||
this.defaultEventTimeout = defaultEventTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SessionEventSimpleConsumers instance with default values.
|
||||
*/
|
||||
public SessionEventSimpleConsumers() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SessionEventSimpleConsumers instance with the specified parameters.
|
||||
*
|
||||
* @param defaultEventTimeout The default event timeout
|
||||
* @param assistantContentConsumers The assistant content consumers
|
||||
*/
|
||||
public SessionEventSimpleConsumers(Timeout defaultEventTimeout, AssistantContentConsumers assistantContentConsumers) {
|
||||
Validate.notNull(defaultEventTimeout, "defaultEventTimeout can't be null");
|
||||
Validate.notNull(assistantContentConsumers, "assistantContentConsumers can't be null");
|
||||
this.defaultEventTimeout = defaultEventTimeout;
|
||||
this.assistantContentConsumers = assistantContentConsumers;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default event timeout.
|
||||
*/
|
||||
protected Timeout defaultEventTimeout = Timeout.TIMEOUT_180_SECONDS;
|
||||
/**
|
||||
* The assistant content consumers.
|
||||
*/
|
||||
protected AssistantContentConsumers assistantContentConsumers = new AssistantContentSimpleConsumers();
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionEventSimpleConsumers.class);
|
||||
|
||||
/**
|
||||
* Sets the assistant content consumers.
|
||||
*
|
||||
* @param assistantContentConsumers The assistant content consumers
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public SessionEventSimpleConsumers setAssistantContentConsumer(AssistantContentConsumers assistantContentConsumers) {
|
||||
Validate.notNull(assistantContentConsumers, "assistantContentConsumers can't be null");
|
||||
this.assistantContentConsumers = assistantContentConsumers;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the assistant content consumers.
|
||||
*
|
||||
* @return The assistant content consumers
|
||||
*/
|
||||
public AssistantContentConsumers getAssistantContentConsumers() {
|
||||
return assistantContentConsumers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.alibaba.qwen.code.cli.session.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a session control operation fails.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class SessionControlException extends Exception {
|
||||
/**
|
||||
* Creates a new exception.
|
||||
*/
|
||||
public SessionControlException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception with a message.
|
||||
*
|
||||
* @param message The exception message
|
||||
*/
|
||||
public SessionControlException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception with a message and cause.
|
||||
*
|
||||
* @param message The exception message
|
||||
* @param cause The exception cause
|
||||
*/
|
||||
public SessionControlException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception with a cause.
|
||||
*
|
||||
* @param cause The exception cause
|
||||
*/
|
||||
public SessionControlException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception with all parameters.
|
||||
*
|
||||
* @param message The exception message
|
||||
* @param cause The exception cause
|
||||
* @param enableSuppression Whether suppression is enabled
|
||||
* @param writableStackTrace Whether the stack trace is writable
|
||||
*/
|
||||
public SessionControlException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.alibaba.qwen.code.cli.session.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when sending a prompt in a session fails.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class SessionSendPromptException extends Exception {
|
||||
/**
|
||||
* Creates a new exception.
|
||||
*/
|
||||
public SessionSendPromptException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception with a message.
|
||||
*
|
||||
* @param message The exception message
|
||||
*/
|
||||
public SessionSendPromptException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception with a message and cause.
|
||||
*
|
||||
* @param message The exception message
|
||||
* @param cause The exception cause
|
||||
*/
|
||||
public SessionSendPromptException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception with a cause.
|
||||
*
|
||||
* @param cause The exception cause
|
||||
*/
|
||||
public SessionSendPromptException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exception with all parameters.
|
||||
*
|
||||
* @param message The exception message
|
||||
* @param cause The exception cause
|
||||
* @param enableSuppression Whether suppression is enabled
|
||||
* @param writableStackTrace Whether the stack trace is writable
|
||||
*/
|
||||
public SessionSendPromptException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.alibaba.qwen.code.cli.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Defines the contract for communication with the Qwen Code CLI.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public interface Transport {
|
||||
/**
|
||||
* Gets the transport options used by this transport.
|
||||
*
|
||||
* @return The transport options
|
||||
*/
|
||||
TransportOptions getTransportOptions();
|
||||
|
||||
/**
|
||||
* Checks if the transport is currently reading.
|
||||
*
|
||||
* @return true if reading, false otherwise
|
||||
*/
|
||||
boolean isReading();
|
||||
|
||||
/**
|
||||
* Starts the transport.
|
||||
*
|
||||
* @throws java.io.IOException if starting fails
|
||||
*/
|
||||
void start() throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the transport and releases resources.
|
||||
*
|
||||
* @throws java.io.IOException if closing fails
|
||||
*/
|
||||
void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Checks if the transport is available for communication.
|
||||
*
|
||||
* @return true if available, false otherwise
|
||||
*/
|
||||
boolean isAvailable();
|
||||
|
||||
/**
|
||||
* Sends a message and waits for a single-line response.
|
||||
*
|
||||
* @param message The message to send
|
||||
* @return The response message
|
||||
* @throws java.io.IOException if an I/O error occurs
|
||||
* @throws java.util.concurrent.ExecutionException if an execution error occurs
|
||||
* @throws java.lang.InterruptedException if the operation is interrupted
|
||||
* @throws java.util.concurrent.TimeoutException if the operation times out
|
||||
*/
|
||||
String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException;
|
||||
|
||||
/**
|
||||
* Sends a message and waits for a multi-line response.
|
||||
*
|
||||
* @param message The message to send
|
||||
* @param callBackFunction A function to process each line of the response
|
||||
* @throws java.io.IOException if an I/O error occurs
|
||||
*/
|
||||
void inputWaitForMultiLine(String message, Function<String, Boolean> callBackFunction) throws IOException;
|
||||
|
||||
/**
|
||||
* Sends a message without waiting for a response.
|
||||
*
|
||||
* @param message The message to send
|
||||
* @throws java.io.IOException if an I/O error occurs
|
||||
*/
|
||||
void inputNoWaitResponse(String message) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
package com.alibaba.qwen.code.cli.transport;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
/**
|
||||
* Configuration options for the transport layer.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class TransportOptions implements Cloneable {
|
||||
/**
|
||||
* Path to the Qwen executable.
|
||||
*/
|
||||
private String pathToQwenExecutable;
|
||||
/**
|
||||
* Current working directory for the CLI process.
|
||||
*/
|
||||
private String cwd;
|
||||
/**
|
||||
* Model to use for the session.
|
||||
*/
|
||||
private String model;
|
||||
/**
|
||||
* Permission mode for the session.
|
||||
*/
|
||||
private PermissionMode permissionMode;
|
||||
/**
|
||||
* Environment variables to pass to the CLI process.
|
||||
*/
|
||||
private Map<String, String> env;
|
||||
/**
|
||||
* Maximum number of turns in a session.
|
||||
*/
|
||||
private Integer maxSessionTurns;
|
||||
/**
|
||||
* List of core tools to enable.
|
||||
*/
|
||||
private List<String> coreTools;
|
||||
/**
|
||||
* List of tools to exclude.
|
||||
*/
|
||||
private List<String> excludeTools;
|
||||
/**
|
||||
* List of tools that are allowed.
|
||||
*/
|
||||
private List<String> allowedTools;
|
||||
/**
|
||||
* Authentication type to use.
|
||||
*/
|
||||
private String authType;
|
||||
/**
|
||||
* Whether to include partial messages in responses.
|
||||
*/
|
||||
private Boolean includePartialMessages;
|
||||
/**
|
||||
* Whether to enable skills.
|
||||
*/
|
||||
private Boolean skillsEnable;
|
||||
/**
|
||||
* Timeout for individual turns.
|
||||
*/
|
||||
private Timeout turnTimeout;
|
||||
/**
|
||||
* Timeout for messages.
|
||||
*/
|
||||
private Timeout messageTimeout;
|
||||
/**
|
||||
* Session ID to resume.
|
||||
*/
|
||||
private String resumeSessionId;
|
||||
/**
|
||||
* Additional options to pass to the CLI.
|
||||
*/
|
||||
private List<String> otherOptions;
|
||||
|
||||
/**
|
||||
* Gets the path to the Qwen executable.
|
||||
*
|
||||
* @return The path to the Qwen executable
|
||||
*/
|
||||
public String getPathToQwenExecutable() {
|
||||
return pathToQwenExecutable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the Qwen executable.
|
||||
*
|
||||
* @param pathToQwenExecutable The path to the Qwen executable
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setPathToQwenExecutable(String pathToQwenExecutable) {
|
||||
this.pathToQwenExecutable = pathToQwenExecutable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current working directory.
|
||||
*
|
||||
* @return The current working directory
|
||||
*/
|
||||
public String getCwd() {
|
||||
return cwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current working directory.
|
||||
*
|
||||
* @param cwd The current working directory
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setCwd(String cwd) {
|
||||
this.cwd = cwd;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model to use.
|
||||
*
|
||||
* @return The model name
|
||||
*/
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model to use.
|
||||
*
|
||||
* @param model The model name
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setModel(String model) {
|
||||
this.model = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permission mode.
|
||||
*
|
||||
* @return The permission mode
|
||||
*/
|
||||
public PermissionMode getPermissionMode() {
|
||||
return permissionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the permission mode.
|
||||
*
|
||||
* @param permissionMode The permission mode
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setPermissionMode(PermissionMode permissionMode) {
|
||||
this.permissionMode = permissionMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the environment variables.
|
||||
*
|
||||
* @return A map of environment variables
|
||||
*/
|
||||
public Map<String, String> getEnv() {
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the environment variables.
|
||||
*
|
||||
* @param env A map of environment variables
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setEnv(Map<String, String> env) {
|
||||
this.env = env;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of session turns.
|
||||
*
|
||||
* @return The maximum number of session turns
|
||||
*/
|
||||
public Integer getMaxSessionTurns() {
|
||||
return maxSessionTurns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of session turns.
|
||||
*
|
||||
* @param maxSessionTurns The maximum number of session turns
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setMaxSessionTurns(Integer maxSessionTurns) {
|
||||
this.maxSessionTurns = maxSessionTurns;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of core tools.
|
||||
*
|
||||
* @return The list of core tools
|
||||
*/
|
||||
public List<String> getCoreTools() {
|
||||
return coreTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of core tools.
|
||||
*
|
||||
* @param coreTools The list of core tools
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setCoreTools(List<String> coreTools) {
|
||||
this.coreTools = coreTools;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of excluded tools.
|
||||
*
|
||||
* @return The list of excluded tools
|
||||
*/
|
||||
public List<String> getExcludeTools() {
|
||||
return excludeTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of excluded tools.
|
||||
*
|
||||
* @param excludeTools The list of excluded tools
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setExcludeTools(List<String> excludeTools) {
|
||||
this.excludeTools = excludeTools;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of allowed tools.
|
||||
*
|
||||
* @return The list of allowed tools
|
||||
*/
|
||||
public List<String> getAllowedTools() {
|
||||
return allowedTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of allowed tools.
|
||||
*
|
||||
* @param allowedTools The list of allowed tools
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setAllowedTools(List<String> allowedTools) {
|
||||
this.allowedTools = allowedTools;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the authentication type.
|
||||
*
|
||||
* @return The authentication type
|
||||
*/
|
||||
public String getAuthType() {
|
||||
return authType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authentication type.
|
||||
*
|
||||
* @param authType The authentication type
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setAuthType(String authType) {
|
||||
this.authType = authType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether to include partial messages.
|
||||
*
|
||||
* @return Whether to include partial messages
|
||||
*/
|
||||
public Boolean getIncludePartialMessages() {
|
||||
return includePartialMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to include partial messages.
|
||||
*
|
||||
* @param includePartialMessages Whether to include partial messages
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setIncludePartialMessages(Boolean includePartialMessages) {
|
||||
this.includePartialMessages = includePartialMessages;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether skills are enabled.
|
||||
*
|
||||
* @return Whether skills are enabled
|
||||
*/
|
||||
public Boolean getSkillsEnable() {
|
||||
return skillsEnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether skills are enabled.
|
||||
*
|
||||
* @param skillsEnable Whether skills are enabled
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setSkillsEnable(Boolean skillsEnable) {
|
||||
this.skillsEnable = skillsEnable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the turn timeout.
|
||||
*
|
||||
* @return The turn timeout
|
||||
*/
|
||||
public Timeout getTurnTimeout() {
|
||||
return turnTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the turn timeout.
|
||||
*
|
||||
* @param turnTimeout The turn timeout
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setTurnTimeout(Timeout turnTimeout) {
|
||||
this.turnTimeout = turnTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message timeout.
|
||||
*
|
||||
* @return The message timeout
|
||||
*/
|
||||
public Timeout getMessageTimeout() {
|
||||
return messageTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message timeout.
|
||||
*
|
||||
* @param messageTimeout The message timeout
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setMessageTimeout(Timeout messageTimeout) {
|
||||
this.messageTimeout = messageTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session ID to resume.
|
||||
*
|
||||
* @return The session ID to resume
|
||||
*/
|
||||
public String getResumeSessionId() {
|
||||
return resumeSessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session ID to resume.
|
||||
*
|
||||
* @param resumeSessionId The session ID to resume
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setResumeSessionId(String resumeSessionId) {
|
||||
this.resumeSessionId = resumeSessionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets additional options.
|
||||
*
|
||||
* @return Additional options
|
||||
*/
|
||||
public List<String> getOtherOptions() {
|
||||
return otherOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets additional options.
|
||||
*
|
||||
* @param otherOptions Additional options
|
||||
* @return This instance for method chaining
|
||||
*/
|
||||
public TransportOptions setOtherOptions(List<String> otherOptions) {
|
||||
this.otherOptions = otherOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public TransportOptions clone() {
|
||||
try {
|
||||
return (TransportOptions) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package com.alibaba.qwen.code.cli.transport.process;
|
||||
|
||||
import com.alibaba.qwen.code.cli.transport.Transport;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.utils.MyConcurrentUtils;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
import org.apache.commons.lang3.exception.ContextedRuntimeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.lang.ProcessBuilder.Redirect;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation of the Transport interface that communicates with the Qwen CLI via a process.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class ProcessTransport implements Transport {
|
||||
private static final Logger log = LoggerFactory.getLogger(ProcessTransport.class);
|
||||
private final TransportOptions transportOptions;
|
||||
protected Timeout turnTimeout;
|
||||
protected Timeout messageTimeout;
|
||||
|
||||
protected Process process;
|
||||
protected BufferedWriter processInput;
|
||||
protected BufferedReader processOutput;
|
||||
protected BufferedReader processError;
|
||||
protected final Consumer<String> errorHandler;
|
||||
|
||||
private final AtomicBoolean reading = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Constructs a new ProcessTransport with default options.
|
||||
*
|
||||
* @throws java.io.IOException if starting the process fails
|
||||
*/
|
||||
public ProcessTransport() throws IOException {
|
||||
this(new TransportOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new ProcessTransport with the specified options.
|
||||
*
|
||||
* @param transportOptions The transport options to use
|
||||
* @throws java.io.IOException if starting the process fails
|
||||
*/
|
||||
public ProcessTransport(TransportOptions transportOptions) throws IOException {
|
||||
this(transportOptions, (line) -> log.error("process error: {}", line));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new ProcessTransport with the specified options and error handler.
|
||||
*
|
||||
* @param transportOptions The transport options to use
|
||||
* @param errorHandler The error handler to use
|
||||
* @throws java.io.IOException if starting the process fails
|
||||
*/
|
||||
public ProcessTransport(TransportOptions transportOptions, Consumer<String> errorHandler) throws IOException {
|
||||
this.transportOptions = transportOptions;
|
||||
this.errorHandler = errorHandler;
|
||||
start();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public TransportOptions getTransportOptions() {
|
||||
return transportOptions;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public boolean isReading() {
|
||||
return reading.get();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void start() throws IOException {
|
||||
TransportOptionsAdapter transportOptionsAdapter = new TransportOptionsAdapter(transportOptions);
|
||||
this.turnTimeout = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeout();
|
||||
this.messageTimeout = transportOptionsAdapter.getHandledTransportOptions().getMessageTimeout();
|
||||
|
||||
String[] commandArgs = transportOptionsAdapter.buildCommandArgs();
|
||||
log.debug("trans to command args: {}", transportOptionsAdapter);
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(commandArgs)
|
||||
.redirectOutput(Redirect.PIPE)
|
||||
.redirectInput(Redirect.PIPE)
|
||||
.redirectError(Redirect.PIPE)
|
||||
.redirectErrorStream(false)
|
||||
.directory(new File(transportOptionsAdapter.getCwd()));
|
||||
|
||||
process = processBuilder.start();
|
||||
processInput = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
|
||||
processOutput = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
processError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||
startErrorReading();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (processInput != null) {
|
||||
processInput.close();
|
||||
}
|
||||
if (processOutput != null) {
|
||||
processOutput.close();
|
||||
}
|
||||
if (processError != null) {
|
||||
processError.close();
|
||||
}
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return process != null && process.isAlive();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException {
|
||||
return inputWaitForOneLine(message, turnTimeout);
|
||||
}
|
||||
|
||||
private String inputWaitForOneLine(String message, Timeout timeOut)
|
||||
throws IOException, TimeoutException, InterruptedException, ExecutionException {
|
||||
inputNoWaitResponse(message);
|
||||
try {
|
||||
reading.set(true);
|
||||
String line = MyConcurrentUtils.runAndWait(() -> {
|
||||
try {
|
||||
return processOutput.readLine();
|
||||
} catch (IOException e) {
|
||||
throw new ContextedRuntimeException("read line error", e)
|
||||
.addContextValue("message", message);
|
||||
}
|
||||
}, timeOut);
|
||||
log.info("inputWaitForOneLine result: {}", line);
|
||||
return line;
|
||||
} finally {
|
||||
reading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void inputWaitForMultiLine(String message, Function<String, Boolean> callBackFunction) throws IOException {
|
||||
inputWaitForMultiLine(message, callBackFunction, turnTimeout);
|
||||
}
|
||||
|
||||
private void inputWaitForMultiLine(String message, Function<String, Boolean> callBackFunction, Timeout timeOut) throws IOException {
|
||||
log.debug("input message for multiLine: {}", message);
|
||||
inputNoWaitResponse(message);
|
||||
MyConcurrentUtils.runAndWait(() -> iterateOutput(callBackFunction), timeOut);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void inputNoWaitResponse(String message) throws IOException {
|
||||
log.debug("input message to process: {}", message);
|
||||
processInput.write(message);
|
||||
processInput.newLine();
|
||||
processInput.flush();
|
||||
}
|
||||
|
||||
private void startErrorReading() {
|
||||
MyConcurrentUtils.asyncRun(() -> {
|
||||
try {
|
||||
for (;;) {
|
||||
final String line = processError.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
if (errorHandler != null) {
|
||||
try {
|
||||
MyConcurrentUtils.runAndWait(() -> errorHandler.accept(line), messageTimeout);
|
||||
} catch (Exception e) {
|
||||
log.warn("error handler error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed read error {}, caused by {}", e.getMessage(), e.getCause(), e);
|
||||
}
|
||||
}, (e, t) -> log.warn("read error {}", t.getMessage(), t));
|
||||
}
|
||||
|
||||
private void iterateOutput(Function<String, Boolean> callBackFunction) {
|
||||
try {
|
||||
reading.set(true);
|
||||
MyConcurrentUtils.runAndWait(() -> {
|
||||
try {
|
||||
for (String line = processOutput.readLine(); line != null; line = processOutput.readLine()) {
|
||||
log.debug("read a message from process {}", line);
|
||||
if (callBackFunction.apply(line)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("read process output error", e);
|
||||
}
|
||||
}, messageTimeout);
|
||||
} finally {
|
||||
reading.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.alibaba.qwen.code.cli.transport.process;
|
||||
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Adapter that converts TransportOptions to command-line arguments for the CLI process.
|
||||
*/
|
||||
class TransportOptionsAdapter {
|
||||
/**
|
||||
* The adapted transport options.
|
||||
*/
|
||||
TransportOptions transportOptions;
|
||||
/**
|
||||
* Default timeout for turns.
|
||||
*/
|
||||
private static final Timeout DEFAULT_TURN_TIMEOUT = new Timeout(1000 * 60 * 30L, TimeUnit.MILLISECONDS);
|
||||
/**
|
||||
* Default timeout for messages.
|
||||
*/
|
||||
private static final Timeout DEFAULT_MESSAGE_TIMEOUT = new Timeout(1000 * 60 * 3L, TimeUnit.MILLISECONDS);
|
||||
|
||||
/**
|
||||
* Constructs a new adapter with the specified options.
|
||||
*
|
||||
* @param userTransportOptions The user's transport options
|
||||
*/
|
||||
TransportOptionsAdapter(TransportOptions userTransportOptions) {
|
||||
transportOptions = addDefaultTransportOptions(userTransportOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the processed transport options.
|
||||
*
|
||||
* @return The processed transport options
|
||||
*/
|
||||
TransportOptions getHandledTransportOptions() {
|
||||
return transportOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current working directory.
|
||||
*
|
||||
* @return The current working directory
|
||||
*/
|
||||
String getCwd() {
|
||||
return transportOptions.getCwd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds command-line arguments from the transport options.
|
||||
*
|
||||
* @return An array of command-line arguments
|
||||
*/
|
||||
String[] buildCommandArgs() {
|
||||
List<String> args = new ArrayList<>(
|
||||
Arrays.asList(transportOptions.getPathToQwenExecutable(), "--input-format", "stream-json", "--output-format",
|
||||
"stream-json", "--channel=SDK"));
|
||||
|
||||
if (StringUtils.isNotBlank(transportOptions.getModel())) {
|
||||
args.add("--model");
|
||||
args.add(transportOptions.getModel());
|
||||
}
|
||||
|
||||
if (transportOptions.getPermissionMode() != null) {
|
||||
args.add("--approval-mode");
|
||||
args.add(transportOptions.getPermissionMode().getValue());
|
||||
}
|
||||
|
||||
if (transportOptions.getMaxSessionTurns() != null) {
|
||||
args.add("--max-session-turns");
|
||||
args.add(transportOptions.getMaxSessionTurns().toString());
|
||||
}
|
||||
|
||||
if (transportOptions.getCoreTools() != null && !transportOptions.getCoreTools().isEmpty()) {
|
||||
args.add("--core-tools");
|
||||
args.add(String.join(",", transportOptions.getCoreTools()));
|
||||
}
|
||||
|
||||
if (transportOptions.getExcludeTools() != null && !transportOptions.getExcludeTools().isEmpty()) {
|
||||
args.add("--exclude-tools");
|
||||
args.add(String.join(",", transportOptions.getExcludeTools()));
|
||||
}
|
||||
|
||||
if (transportOptions.getAllowedTools() != null && !transportOptions.getAllowedTools().isEmpty()) {
|
||||
args.add("--allowed-tools");
|
||||
args.add(String.join(",", transportOptions.getAllowedTools()));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(transportOptions.getAuthType())) {
|
||||
args.add("--auth-type");
|
||||
args.add(transportOptions.getAuthType());
|
||||
}
|
||||
|
||||
if (transportOptions.getIncludePartialMessages() != null && transportOptions.getIncludePartialMessages()) {
|
||||
args.add("--include-partial-messages");
|
||||
}
|
||||
|
||||
if (transportOptions.getSkillsEnable() != null && transportOptions.getSkillsEnable()) {
|
||||
args.add("--experimental-skills");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(transportOptions.getResumeSessionId())) {
|
||||
args.add("--resume");
|
||||
args.add(transportOptions.getResumeSessionId());
|
||||
}
|
||||
|
||||
if (transportOptions.getOtherOptions() != null) {
|
||||
args.addAll(transportOptions.getOtherOptions());
|
||||
}
|
||||
return args.toArray(new String[] {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default values to the user's transport options.
|
||||
*
|
||||
* @param userTransportOptions The user's transport options
|
||||
* @return The options with defaults added
|
||||
*/
|
||||
private TransportOptions addDefaultTransportOptions(TransportOptions userTransportOptions) {
|
||||
TransportOptions transportOptions = Optional.ofNullable(userTransportOptions)
|
||||
.map(TransportOptions::clone)
|
||||
.orElse(new TransportOptions());
|
||||
|
||||
if (StringUtils.isBlank(transportOptions.getPathToQwenExecutable())) {
|
||||
transportOptions.setPathToQwenExecutable("qwen");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(transportOptions.getCwd())) {
|
||||
transportOptions.setCwd(new File("").getAbsolutePath());
|
||||
}
|
||||
|
||||
Map<String, String> env = new HashMap<>(System.getenv());
|
||||
Optional.ofNullable(transportOptions.getEnv()).ifPresent(env::putAll);
|
||||
transportOptions.setEnv(env);
|
||||
|
||||
if (transportOptions.getTurnTimeout() == null) {
|
||||
transportOptions.setTurnTimeout(DEFAULT_TURN_TIMEOUT);
|
||||
}
|
||||
|
||||
if (transportOptions.getMessageTimeout() == null) {
|
||||
transportOptions.setMessageTimeout(DEFAULT_MESSAGE_TIMEOUT);
|
||||
}
|
||||
return transportOptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.alibaba.qwen.code.cli.utils;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Utility class for concurrent operations.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class MyConcurrentUtils {
|
||||
private static final Logger log = LoggerFactory.getLogger(MyConcurrentUtils.class);
|
||||
|
||||
/**
|
||||
* Runs a task and waits for it to complete with a timeout.
|
||||
*
|
||||
* @param runnable The task to run
|
||||
* @param timeOut The timeout for the operation
|
||||
*/
|
||||
public static void runAndWait(Runnable runnable, Timeout timeOut) {
|
||||
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}, ThreadPoolConfig.getExecutor());
|
||||
try {
|
||||
future.get(timeOut.getValue(), timeOut.getUnit());
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("task interrupted", e);
|
||||
future.cancel(true);
|
||||
} catch (TimeoutException e) {
|
||||
log.warn("Operation timed out", e);
|
||||
future.cancel(true);
|
||||
} catch (Exception e) {
|
||||
future.cancel(true);
|
||||
log.warn("Operation error", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task that returns a value and waits for it to complete with a timeout.
|
||||
*
|
||||
* @param supplier The task to run
|
||||
* @param timeOut The timeout for the operation
|
||||
* @param <T> The type of the result
|
||||
* @return The result of the task
|
||||
* @throws java.util.concurrent.ExecutionException if an execution error occurs
|
||||
* @throws java.lang.InterruptedException if the operation is interrupted
|
||||
* @throws java.util.concurrent.TimeoutException if the operation times out
|
||||
*/
|
||||
public static <T> T runAndWait(Supplier<T> supplier, Timeout timeOut)
|
||||
throws ExecutionException, InterruptedException, TimeoutException {
|
||||
CompletableFuture<T> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return supplier.get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}, ThreadPoolConfig.getExecutor());
|
||||
|
||||
try {
|
||||
return future.get(timeOut.getValue(), timeOut.getUnit());
|
||||
} catch (TimeoutException | InterruptedException | ExecutionException e) {
|
||||
future.cancel(true);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task asynchronously with an error callback.
|
||||
*
|
||||
* @param runnable The task to run
|
||||
* @param errorCallback The error callback
|
||||
*/
|
||||
public static void asyncRun(Runnable runnable, BiConsumer<Void, Throwable> errorCallback) {
|
||||
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
log.warn("async task error", e);
|
||||
}
|
||||
}, ThreadPoolConfig.getExecutor());
|
||||
future.whenComplete(errorCallback);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.alibaba.qwen.code.cli.utils;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Configuration for the thread pool used by the SDK.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class ThreadPoolConfig {
|
||||
private static final ThreadPoolExecutor defaultExecutor = new ThreadPoolExecutor(
|
||||
30, 100, 60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(300),
|
||||
new ThreadFactory() {
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r, "qwen_code_cli-pool-" + threadNumber.getAndIncrement());
|
||||
t.setDaemon(false);
|
||||
return t;
|
||||
}
|
||||
},
|
||||
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
|
||||
);
|
||||
|
||||
private static Supplier<ThreadPoolExecutor> executorSupplier;
|
||||
|
||||
/**
|
||||
* Sets the supplier for the executor.
|
||||
*
|
||||
* @param executorSupplier The supplier for the executor
|
||||
*/
|
||||
public static void setExecutorSupplier(Supplier<ThreadPoolExecutor> executorSupplier) {
|
||||
ThreadPoolConfig.executorSupplier = executorSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default executor.
|
||||
*
|
||||
* @return The default executor
|
||||
*/
|
||||
public static ThreadPoolExecutor getDefaultExecutor() {
|
||||
return defaultExecutor;
|
||||
}
|
||||
|
||||
static ExecutorService getExecutor() {
|
||||
return Optional.ofNullable(executorSupplier).map(s -> {
|
||||
try {
|
||||
return s.get();
|
||||
} catch (Exception e) {
|
||||
return defaultExecutor;
|
||||
}
|
||||
}).orElse(defaultExecutor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.alibaba.qwen.code.cli.utils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
/**
|
||||
* Represents a timeout value with a time unit.
|
||||
*
|
||||
* @author skyfire
|
||||
* @version $Id: 0.0.1
|
||||
*/
|
||||
public class Timeout {
|
||||
/**
|
||||
* The timeout value.
|
||||
*/
|
||||
private final Long value;
|
||||
/**
|
||||
* The time unit.
|
||||
*/
|
||||
private final TimeUnit unit;
|
||||
|
||||
/**
|
||||
* Creates a new Timeout instance.
|
||||
*
|
||||
* @param value The timeout value
|
||||
* @param unit The time unit
|
||||
*/
|
||||
public Timeout(Long value, TimeUnit unit) {
|
||||
Validate.notNull(value, "value can not be null");
|
||||
Validate.notNull(unit, "unit can not be null");
|
||||
this.value = value;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timeout value.
|
||||
*
|
||||
* @return The timeout value
|
||||
*/
|
||||
public Long getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time unit.
|
||||
*
|
||||
* @return The time unit
|
||||
*/
|
||||
public TimeUnit getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* A timeout of 60 seconds.
|
||||
*/
|
||||
public static final Timeout TIMEOUT_60_SECONDS = new Timeout(60L, TimeUnit.SECONDS);
|
||||
|
||||
/**
|
||||
* A timeout of 180 seconds.
|
||||
*/
|
||||
public static final Timeout TIMEOUT_180_SECONDS = new Timeout(180L, TimeUnit.SECONDS);
|
||||
|
||||
/**
|
||||
* A timeout of 30 minutes.
|
||||
*/
|
||||
public static final Timeout TIMEOUT_30_MINUTES = new Timeout(60L, TimeUnit.MINUTES);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.alibaba.qwen.code.cli;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class QwenCodeCliTest {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(QwenCodeCliTest.class);
|
||||
@Test
|
||||
void simpleQuery() {
|
||||
List<String> result = QwenCodeCli.simpleQuery("hello world");
|
||||
log.info("simpleQuery result: {}", result);
|
||||
assertNotNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleQueryWithModel() {
|
||||
List<String> result = QwenCodeCli.simpleQuery("hello world", new TransportOptions().setModel("qwen-plus"));
|
||||
log.info("simpleQueryWithModel result: {}", result);
|
||||
assertNotNull(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.alibaba.qwen.code.cli.example;
|
||||
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.AssistantContentSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class QuickStartExample {
|
||||
private static final Logger logger = LoggerFactory.getLogger(QuickStartExample.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("runSimpleExample started.{}", StringUtils.repeat("=", 150));
|
||||
runSimpleExample();
|
||||
|
||||
logger.info("runTransportOptionsExample started. {}", StringUtils.repeat("=", 150));
|
||||
runTransportOptionsExample();
|
||||
|
||||
logger.info("runStreamingExample started. {}", StringUtils.repeat("=", 150));
|
||||
runStreamingExample();
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple example showing basic query usage
|
||||
*/
|
||||
public static void runSimpleExample() {
|
||||
List<String> result = QwenCodeCli.simpleQuery("hello world");
|
||||
result.forEach(logger::info);
|
||||
}
|
||||
|
||||
/**
|
||||
* TransportOptions example showing comprehensive transport options configuration
|
||||
*/
|
||||
public static void runTransportOptionsExample() {
|
||||
TransportOptions options = new TransportOptions()
|
||||
.setModel("qwen3-coder-flash")
|
||||
.setPermissionMode(PermissionMode.AUTO_EDIT)
|
||||
.setCwd("./")
|
||||
.setEnv(new HashMap<String, String>() {{put("CUSTOM_VAR", "value");}})
|
||||
.setIncludePartialMessages(true)
|
||||
.setTurnTimeout(new Timeout(120L, TimeUnit.SECONDS))
|
||||
.setMessageTimeout(new Timeout(90L, TimeUnit.SECONDS))
|
||||
.setAllowedTools(Arrays.asList("read_file", "write_file", "list_directory"));
|
||||
|
||||
List<String> result = QwenCodeCli.simpleQuery("who are you, what are your capabilities?", options);
|
||||
result.forEach(logger::info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming example showing simple query usage
|
||||
*/
|
||||
public static void runStreamingExample() {
|
||||
QwenCodeCli.simpleQuery("who are you, what are your capabilities?",
|
||||
new TransportOptions().setMessageTimeout(new Timeout(10L, TimeUnit.SECONDS)), new AssistantContentSimpleConsumers() {
|
||||
|
||||
@Override
|
||||
public void onText(Session session, TextAssistantContent textAssistantContent) {
|
||||
logger.info("Text content received: {}", textAssistantContent.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) {
|
||||
logger.info("Thinking content received: {}", thingkingAssistantContent.getThinking());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolUse(Session session, ToolUseAssistantContent toolUseContent) {
|
||||
logger.info("Tool use content received: {} with arguments: {}",
|
||||
toolUseContent, toolUseContent.getInput());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolResult(Session session, ToolResultAssistantContent toolResultContent) {
|
||||
logger.info("Tool result content received: {}", toolResultContent.getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOtherContent(Session session, AssistantContent<?> other) {
|
||||
logger.info("Other content received: {}", other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsage(Session session, AssistantUsage assistantUsage) {
|
||||
logger.info("Usage information received: Input tokens: {}, Output tokens: {}",
|
||||
assistantUsage.getUsage().getInputTokens(), assistantUsage.getUsage().getOutputTokens());
|
||||
}
|
||||
}.setDefaultPermissionOperation(Operation.allow));
|
||||
logger.info("Streaming example completed.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.alibaba.qwen.code.cli.example;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKPartialAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.AssistantContentSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.block.TextBlock;
|
||||
import com.alibaba.qwen.code.cli.session.exception.SessionControlException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class SessionExample {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SessionExample.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
Session session = QwenCodeCli.newSession();
|
||||
try {
|
||||
logger.info("runPermissionModeExample started {}", StringUtils.repeat("=", 150));
|
||||
runPermissionModeExample(session);
|
||||
|
||||
logger.info("runSetModelExample started {}", StringUtils.repeat("=", 150));
|
||||
runSetModelExample(session);
|
||||
|
||||
logger.info("runSetPermissionModeExample started {}", StringUtils.repeat("=", 150));
|
||||
runSetPermissionModeExample(session);
|
||||
|
||||
logger.info("runInterruptExample started {}", StringUtils.repeat("=", 150));
|
||||
runInterruptExample(session);
|
||||
|
||||
logger.info("runSetModelExample started {}", StringUtils.repeat("=", 150));
|
||||
runSetModelExample(session);
|
||||
|
||||
logger.info("runPromptUseLowLevelEventExample started {}", StringUtils.repeat("=", 150));
|
||||
runPromptUseLowLevelEventExample(session);
|
||||
|
||||
logger.info("runPromptUseHighLevelEventExample started {}", StringUtils.repeat("=", 150));
|
||||
runPromptUseHighLevelEventExample(session);
|
||||
|
||||
System.exit(0);
|
||||
} finally {
|
||||
try {
|
||||
session.close();
|
||||
} catch (SessionControlException e) {
|
||||
logger.error("Error closing session", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example showing how to set different permission modes
|
||||
*/
|
||||
public static void runPermissionModeExample(Session session) {
|
||||
try {
|
||||
logger.info(session.setPermissionMode(PermissionMode.PLAN).map(s -> s ? "Permission mode set to PLAN" : "Permission mode set error")
|
||||
.orElse("Permission mode set unknown"));
|
||||
} catch (SessionControlException e) {
|
||||
logger.error("Error setting permission mode", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example showing how to interrupt a running prompt
|
||||
*/
|
||||
public static void runInterruptExample(Session session) {
|
||||
try {
|
||||
session.sendPrompt("Analyze this large codebase...", new SessionEventSimpleConsumers() {
|
||||
@Override
|
||||
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
|
||||
String message = assistantMessage.getMessage().getContent().stream()
|
||||
.findFirst()
|
||||
.filter(content -> content instanceof TextBlock)
|
||||
.map(content -> ((TextBlock) content).getText())
|
||||
.orElse("");
|
||||
logger.info("Received: {}", message);
|
||||
|
||||
// Interrupt the session after receiving the first message
|
||||
try {
|
||||
Optional<Boolean> interruptResult = session.interrupt();
|
||||
logger.info("{}", interruptResult.map(s -> s ? "Interrupt successful" : "Interrupt error")
|
||||
.orElse("Interrupt unknown"));
|
||||
} catch (SessionControlException e) {
|
||||
logger.error("Interrupt error: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while sending the prompt", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example showing how to dynamically change the AI model during a session
|
||||
*/
|
||||
public static void runSetModelExample(Session session) {
|
||||
try {
|
||||
// Switch to a specific model
|
||||
Optional<Boolean> modelChangeResult = session.setModel("qwen3-coder-flash");
|
||||
logger.info("{}", modelChangeResult.map(s -> s ? "setModel success" : "setModel error")
|
||||
.orElse("setModel unknown"));
|
||||
|
||||
// Use the model for a prompt
|
||||
session.sendPrompt("hello world", new SessionEventSimpleConsumers());
|
||||
|
||||
// Switch to another model
|
||||
Optional<Boolean> modelChangeResult2 = session.setModel("qwen3-coder-plus");
|
||||
logger.info("{}", modelChangeResult2.map(s -> s ? "setModel success" : "setModel error")
|
||||
.orElse("setModel unknown"));
|
||||
|
||||
// Use the new model for another prompt
|
||||
session.sendPrompt("list files in the current directory", new SessionEventSimpleConsumers());
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while changing model or sending prompt", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example showing how to dynamically change permission mode during a session
|
||||
*/
|
||||
public static void runSetPermissionModeExample(Session session) {
|
||||
try {
|
||||
// Switch to a permissive mode
|
||||
Optional<Boolean> permissionChangeResult = session.setPermissionMode(PermissionMode.YOLO);
|
||||
logger.info("{}", permissionChangeResult.map(s -> s ? "setPermissionMode success" : "setPermissionMode error")
|
||||
.orElse("setPermissionMode unknown"));
|
||||
|
||||
// Use the session with the new permission mode
|
||||
session.sendPrompt("in the dir src/test/temp/, create file empty file test.touch", new SessionEventSimpleConsumers());
|
||||
|
||||
// Switch to another permission mode
|
||||
Optional<Boolean> permissionChangeResult2 = session.setPermissionMode(PermissionMode.PLAN);
|
||||
logger.info("{}", permissionChangeResult2.map(s -> s ? "setPermissionMode success" : "setPermissionMode error")
|
||||
.orElse("setPermissionMode unknown"));
|
||||
|
||||
// Use the session with the new permission mode
|
||||
session.sendPrompt("rename test.touch to test_rename.touch", new SessionEventSimpleConsumers());
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while changing permission mode or sending prompt", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void runPromptUseLowLevelEventExample(Session session) {
|
||||
try {
|
||||
session.setPermissionMode(PermissionMode.YOLO);
|
||||
session.sendPrompt("devlop Fibonacci function by python", new SessionEventSimpleConsumers() {
|
||||
@Override
|
||||
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
|
||||
logger.info("Received assistantMessage {}", JSON.toJSONString(assistantMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) {
|
||||
logger.info("Received partialAssistantMessage {}", JSON.toJSONString(partialAssistantMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserMessage(Session session, SDKUserMessage userMessage) {
|
||||
logger.info("Received userMessage {}", JSON.toJSONString(userMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOtherMessage(Session session, String message) {
|
||||
logger.info("Received otherMessage {}", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse) {
|
||||
logger.info("Received controlResponse {}", JSON.toJSONString(cliControlResponse));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CLIControlResponse<? extends ControlResponsePayload> onControlRequest(Session session, CLIControlRequest<?> cliControlRequest) {
|
||||
logger.info("Received controlRequest {}", JSON.toJSONString(cliControlRequest));
|
||||
return new CLIControlResponse<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResultMessage(Session session, SDKResultMessage resultMessage) {
|
||||
logger.info("Received resultMessage {}", JSON.toJSONString(resultMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemMessage(Session session, SDKSystemMessage systemMessage) {
|
||||
logger.info("Received systemMessage {}", JSON.toJSONString(systemMessage));
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while sending prompt", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void runPromptUseHighLevelEventExample(Session session) {
|
||||
try {
|
||||
session.sendPrompt("devlop Fibonacci function by python", new SessionEventSimpleConsumers().setAssistantContentConsumer(new AssistantContentSimpleConsumers(){
|
||||
@Override
|
||||
public void onText(Session session, TextAssistantContent textAssistantContent) {
|
||||
logger.info("Received textAssistantContent {}", textAssistantContent.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) {
|
||||
logger.info("Received thingkingAssistantContent {}", thingkingAssistantContent.getThinking());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) {
|
||||
logger.info("Received toolUseAssistantContent {}", toolUseAssistantContent.getInput());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) {
|
||||
logger.info("Received toolResultAssistantContent {}", toolResultAssistantContent.getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOtherContent(Session session, AssistantContent<?> other) {
|
||||
logger.info("Received other {}", other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsage(Session session, AssistantUsage assistantUsage) {
|
||||
logger.info("Received usage {}", assistantUsage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ControlResponsePayload onOtherControlRequest(Session session, ControlRequestPayload requestPayload) {
|
||||
logger.info("Received otherControlRequest {}", requestPayload);
|
||||
return new ControlResponsePayload();
|
||||
}
|
||||
}.setDefaultPermissionOperation(Operation.allow)));
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while sending prompt", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.alibaba.qwen.code.cli.example;
|
||||
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.utils.ThreadPoolConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ThreadPoolConfigurationExample {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ThreadPoolConfigurationExample.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
runModifyDefaultExample();
|
||||
runCustomSupplierExample();
|
||||
}
|
||||
|
||||
/**
|
||||
* Example showing how to set a custom thread pool supplier
|
||||
*/
|
||||
public static void runCustomSupplierExample() {
|
||||
// Set a custom thread pool supplier
|
||||
ThreadPoolConfig.setExecutorSupplier(() -> (ThreadPoolExecutor) Executors.newFixedThreadPool(20));
|
||||
logger.info("Custom thread pool supplier set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Example showing how to modify properties of the default thread pool
|
||||
*/
|
||||
public static void runModifyDefaultExample() {
|
||||
// Get the default executor and modify its properties
|
||||
ThreadPoolExecutor executor = ThreadPoolConfig.getDefaultExecutor();
|
||||
|
||||
// Modify the core pool size
|
||||
executor.setCorePoolSize(15);
|
||||
|
||||
// Modify the maximum pool size
|
||||
executor.setMaximumPoolSize(40);
|
||||
|
||||
// Modify the keep-alive time
|
||||
executor.setKeepAliveTime(120, TimeUnit.SECONDS);
|
||||
|
||||
logger.info("Default thread pool properties modified");
|
||||
|
||||
// The SDK will now use the modified executor for all operations
|
||||
Session session = QwenCodeCli.newSession();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package com.alibaba.qwen.code.cli.session;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.AssistantContentSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.exception.SessionControlException;
|
||||
import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class SessionTest {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionTest.class);
|
||||
|
||||
@Test
|
||||
void partialSendPromptSuccessfully() throws SessionControlException, SessionSendPromptException {
|
||||
Session session = QwenCodeCli.newSession(new TransportOptions().setIncludePartialMessages(true));
|
||||
session.sendPrompt("in the dir src/test/temp/, create file empty file test.touch", new SessionEventSimpleConsumers() {
|
||||
}.setAssistantContentConsumer(new AssistantContentSimpleConsumers() {
|
||||
@Override
|
||||
public void onText(Session session, TextAssistantContent textAssistantContent) {
|
||||
log.info("receive textAssistantContent {}", textAssistantContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) {
|
||||
log.info("receive thingkingAssistantContent {}", thingkingAssistantContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) {
|
||||
log.info("receive toolUseAssistantContent {}", toolUseAssistantContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) {
|
||||
log.info("receive toolResultAssistantContent {}", toolResultAssistantContent);
|
||||
}
|
||||
|
||||
public void onOtherContent(Session session, AssistantContent<?> other) {
|
||||
log.info("receive otherContent {}", other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsage(Session session, AssistantUsage assistantUsage) {
|
||||
log.info("receive assistantUsage {}", assistantUsage);
|
||||
}
|
||||
}.setDefaultPermissionOperation(Operation.allow)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setPermissionModeSuccessfully() throws SessionControlException, SessionSendPromptException {
|
||||
Session session = QwenCodeCli.newSession(new TransportOptions());
|
||||
|
||||
log.info(session.setPermissionMode(PermissionMode.YOLO).map(s -> s ? "setPermissionMode 1 success" : "setPermissionMode 1 error")
|
||||
.orElse("setPermissionMode 1 unknown"));
|
||||
session.sendPrompt("in the dir src/test/temp/, create file empty file test.touch", new SessionEventSimpleConsumers());
|
||||
|
||||
log.info(session.setPermissionMode(PermissionMode.PLAN).map(s -> s ? "setPermissionMode 2 success" : "setPermissionMode 2 error")
|
||||
.orElse("setPermissionMode 2 unknown"));
|
||||
session.sendPrompt("rename test.touch to test_rename.touch", new SessionEventSimpleConsumers());
|
||||
|
||||
log.info(session.setPermissionMode(PermissionMode.AUTO_EDIT).map(s -> s ? "setPermissionMode 3 success" : "setPermissionMode 3 error")
|
||||
.orElse("setPermissionMode 3 unknown"));
|
||||
session.sendPrompt("rename test.touch to test_rename.touch", new SessionEventSimpleConsumers());
|
||||
|
||||
session.sendPrompt("rename test.touch to test_rename.touch again user will allow",
|
||||
new SessionEventSimpleConsumers().setAssistantContentConsumer(new AssistantContentSimpleConsumers().setDefaultPermissionOperation(Operation.allow)));
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendPromptAndSetModelSuccessfully() throws SessionControlException, SessionSendPromptException {
|
||||
Session session = QwenCodeCli.newSession(new TransportOptions());
|
||||
|
||||
log.info(session.setModel("qwen3-coder-flash").map(s -> s ? "setModel 1 success" : "setModel 1 error").orElse("setModel 1 unknown"));
|
||||
writeSplitLine("setModel 1 end");
|
||||
|
||||
session.sendPrompt("hello world", new SessionEventSimpleConsumers());
|
||||
writeSplitLine("prompt 1 end");
|
||||
|
||||
log.info(session.setModel("qwen3-coder-plus").map(s -> s ? "setModel 2 success" : "setModel 2 error").orElse("setModel 2 unknown"));
|
||||
writeSplitLine("setModel 1 end");
|
||||
|
||||
session.sendPrompt("Check how many files are in the current directory", new SessionEventSimpleConsumers());
|
||||
writeSplitLine("prompt 2 end");
|
||||
|
||||
log.info(session.setModel("qwen3-max").map(s -> s ? "setModel 3 success" : "setModel 3 error").orElse("setModel 3 unknown"));
|
||||
writeSplitLine("setModel 1 end");
|
||||
|
||||
session.sendPrompt("Check how many xml files are in the current directory", new SessionEventSimpleConsumers());
|
||||
writeSplitLine("prompt 3 end");
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendPromptAndInterruptContinueSuccessfully() throws SessionControlException, SessionSendPromptException {
|
||||
Session session = QwenCodeCli.newSession();
|
||||
|
||||
SessionEventConsumers sessionEventConsumers = new SessionEventSimpleConsumers() {
|
||||
|
||||
@Override
|
||||
public void onSystemMessage(Session session, SDKSystemMessage systemMessage) {
|
||||
log.info("systemMessage: {}", systemMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResultMessage(Session session, SDKResultMessage resultMessage) {
|
||||
log.info("resultMessage: {}", resultMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
|
||||
log.info("assistantMessage: {}", assistantMessage);
|
||||
try {
|
||||
session.interrupt();
|
||||
} catch (SessionControlException e) {
|
||||
log.error("interrupt error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse) {
|
||||
log.info("cliControlResponse: {}", cliControlResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOtherMessage(Session session, String message) {
|
||||
log.info("otherMessage: {}", message);
|
||||
}
|
||||
}.setDefaultEventTimeout(new Timeout(90L, TimeUnit.SECONDS));
|
||||
|
||||
session.sendPrompt("Check how many files are in the current directory", sessionEventConsumers);
|
||||
writeSplitLine("prompt 1 end");
|
||||
|
||||
session.continueSession();
|
||||
session.sendPrompt("hello world", sessionEventConsumers);
|
||||
writeSplitLine("prompt 2 end");
|
||||
|
||||
session.continueSession();
|
||||
session.sendPrompt("当前目录有多少个java文件", sessionEventConsumers);
|
||||
writeSplitLine("prompt 3 end");
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
public void writeSplitLine(String line) {
|
||||
log.info("{} {}", line, StringUtils.repeat("=", 300));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJSON() {
|
||||
String json
|
||||
= "{\"type\":\"assistant\",\"uuid\":\"ed8374fe-a4eb-4fc0-9780-9bd2fd831cda\","
|
||||
+ "\"session_id\":\"166badc0-e6d3-4978-ae47-4ccd51c468ef\",\"message\":{\"content\":[{\"text\":\"Hello! How can I help you with the"
|
||||
+ " Qwen Code SDK for Java today?\",\"type\":\"text\"}],\"id\":\"ed8374fe-a4eb-4fc0-9780-9bd2fd831cda\","
|
||||
+ "\"model\":\"qwen3-coder-plus\",\"role\":\"assistant\",\"type\":\"message\",\"usage\":{\"cache_read_input_tokens\":12766,"
|
||||
+ "\"input_tokens\":12770,\"output_tokens\":17,\"total_tokens\":12787}}}";
|
||||
SDKAssistantMessage assistantMessage = JSON.parseObject(json, SDKAssistantMessage.class);
|
||||
log.info("the assistantMessage: {}", assistantMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.alibaba.qwen.code.cli.transport;
|
||||
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class PermissionModeTest {
|
||||
|
||||
@Test
|
||||
public void shouldBeReturnQwenPermissionModeValue() {
|
||||
assertEquals("default", PermissionMode.DEFAULT.getValue());
|
||||
assertEquals("plan", PermissionMode.PLAN.getValue());
|
||||
assertEquals("auto-edit", PermissionMode.AUTO_EDIT.getValue());
|
||||
assertEquals("yolo", PermissionMode.YOLO.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.alibaba.qwen.code.cli.transport.process;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlInitializeRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlInitializeResponse;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage;
|
||||
import com.alibaba.qwen.code.cli.transport.Transport;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class ProcessTransportTest {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProcessTransportTest.class);
|
||||
|
||||
@Test
|
||||
void shouldStartAndCloseSuccessfully() throws IOException {
|
||||
TransportOptions transportOptions = new TransportOptions();
|
||||
Transport transport = new ProcessTransport(transportOptions);
|
||||
transport.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInputWaitForOneLineSuccessfully() throws IOException, ExecutionException, InterruptedException, TimeoutException {
|
||||
TransportOptions transportOptions = new TransportOptions();
|
||||
Transport transport = new ProcessTransport(transportOptions);
|
||||
|
||||
String message = "{\"type\": \"control_request\", \"request_id\": \"1\", \"request\": {\"subtype\": \"initialize\"} }";
|
||||
System.out.println(transport.inputWaitForOneLine(message));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInitializeSuccessfully() throws IOException, ExecutionException, InterruptedException, TimeoutException {
|
||||
Transport transport = new ProcessTransport();
|
||||
|
||||
String message = CLIControlRequest.create(new CLIControlInitializeRequest()).toString();
|
||||
String responseMsg = transport.inputWaitForOneLine(message);
|
||||
logger.info("responseMsg: {}", responseMsg);
|
||||
CLIControlResponse<CLIControlInitializeResponse> response = JSON.parseObject(responseMsg,
|
||||
new TypeReference<CLIControlResponse<CLIControlInitializeResponse>>() {});
|
||||
logger.info("response: {}", response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSdkMessageSuccessfully() throws IOException, ExecutionException, InterruptedException, TimeoutException {
|
||||
Transport transport = new ProcessTransport();
|
||||
String message = CLIControlRequest.create(new CLIControlInitializeRequest()).toString();
|
||||
transport.inputWaitForOneLine(message);
|
||||
|
||||
String sessionId = "session-" + UUID.randomUUID().toString();
|
||||
String userMessage = new SDKUserMessage().setSessionId(sessionId).setContent("hello world").toString();
|
||||
transport.inputWaitForMultiLine(userMessage, line -> {
|
||||
return "result".equals(JSON.parseObject(line).getString("type"));
|
||||
});
|
||||
|
||||
String userMessage2 = new SDKUserMessage().setSessionId(sessionId).setContent("请使用中文").toString();
|
||||
transport.inputWaitForMultiLine(userMessage2, line -> {
|
||||
return "result".equals(JSON.parseObject(line).getString("type"));
|
||||
});
|
||||
|
||||
String userMessage3 = new SDKUserMessage().setSessionId(sessionId).setContent("当前工作区有多少个文件").toString();
|
||||
transport.inputWaitForMultiLine(userMessage3, line -> {
|
||||
return "result".equals(JSON.parseObject(line).getString("type"));
|
||||
});
|
||||
|
||||
String userMessage4 = new SDKUserMessage().setSessionId("session-sec" + UUID.randomUUID()).setContent("有多少个xml文件").toString();
|
||||
transport.inputWaitForMultiLine(userMessage4, line -> {
|
||||
return "result".equals(JSON.parseObject(line).getString("type"));
|
||||
});
|
||||
|
||||
transport.inputWaitForOneLine(CLIControlRequest.create(new CLIControlInitializeRequest()).toString());
|
||||
transport.inputWaitForMultiLine(new SDKUserMessage().setContent("您好").toString(),
|
||||
line -> "result".equals(JSON.parseObject(line).getString("type")));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/sdk",
|
||||
"version": "0.1.1",
|
||||
"version": "0.6.0",
|
||||
"description": "TypeScript SDK for programmatic access to qwen-code CLI",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code-test-utils",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -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.5.1",
|
||||
"version": "0.6.0",
|
||||
"publisher": "qwenlm",
|
||||
"icon": "assets/icon.png",
|
||||
"repository": {
|
||||
|
||||
@@ -27,13 +27,14 @@ vi.mock('node:fs/promises', () => ({
|
||||
writeFile: vi.fn(() => Promise.resolve(undefined)),
|
||||
unlink: vi.fn(() => Promise.resolve(undefined)),
|
||||
chmod: vi.fn(() => Promise.resolve(undefined)),
|
||||
mkdir: vi.fn(() => Promise.resolve(undefined)),
|
||||
}));
|
||||
|
||||
vi.mock('node:os', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof os>();
|
||||
return {
|
||||
...actual,
|
||||
tmpdir: vi.fn(() => '/tmp'),
|
||||
homedir: vi.fn(() => '/home/test'),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -128,30 +129,24 @@ describe('IDEServer', () => {
|
||||
);
|
||||
|
||||
const port = getPortFromMock(replaceMock);
|
||||
const expectedPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${port}.json`,
|
||||
);
|
||||
const expectedPpidPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${process.ppid}.json`,
|
||||
const expectedLockFile = path.join(
|
||||
'/home/test',
|
||||
'.qwen',
|
||||
'ide',
|
||||
`${port}.lock`,
|
||||
);
|
||||
const expectedContent = JSON.stringify({
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: expectedWorkspacePaths,
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
ideName: 'VS Code',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
expectedLockFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedLockFile, 0o600);
|
||||
});
|
||||
|
||||
it('should set a single folder path', async () => {
|
||||
@@ -166,30 +161,24 @@ describe('IDEServer', () => {
|
||||
);
|
||||
|
||||
const port = getPortFromMock(replaceMock);
|
||||
const expectedPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${port}.json`,
|
||||
);
|
||||
const expectedPpidPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${process.ppid}.json`,
|
||||
const expectedLockFile = path.join(
|
||||
'/home/test',
|
||||
'.qwen',
|
||||
'ide',
|
||||
`${port}.lock`,
|
||||
);
|
||||
const expectedContent = JSON.stringify({
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: '/foo/bar',
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
ideName: 'VS Code',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
expectedLockFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedLockFile, 0o600);
|
||||
});
|
||||
|
||||
it('should set an empty string if no folders are open', async () => {
|
||||
@@ -204,30 +193,24 @@ describe('IDEServer', () => {
|
||||
);
|
||||
|
||||
const port = getPortFromMock(replaceMock);
|
||||
const expectedPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${port}.json`,
|
||||
);
|
||||
const expectedPpidPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${process.ppid}.json`,
|
||||
const expectedLockFile = path.join(
|
||||
'/home/test',
|
||||
'.qwen',
|
||||
'ide',
|
||||
`${port}.lock`,
|
||||
);
|
||||
const expectedContent = JSON.stringify({
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: '',
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
ideName: 'VS Code',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
expectedLockFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedLockFile, 0o600);
|
||||
});
|
||||
|
||||
it('should update the path when workspace folders change', async () => {
|
||||
@@ -256,30 +239,24 @@ describe('IDEServer', () => {
|
||||
);
|
||||
|
||||
const port = getPortFromMock(replaceMock);
|
||||
const expectedPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${port}.json`,
|
||||
);
|
||||
const expectedPpidPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${process.ppid}.json`,
|
||||
const expectedLockFile = path.join(
|
||||
'/home/test',
|
||||
'.qwen',
|
||||
'ide',
|
||||
`${port}.lock`,
|
||||
);
|
||||
const expectedContent = JSON.stringify({
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: expectedWorkspacePaths,
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
ideName: 'VS Code',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
expectedLockFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedLockFile, 0o600);
|
||||
|
||||
// Simulate removing a folder
|
||||
vscodeMock.workspace.workspaceFolders = [{ uri: { fsPath: '/baz/qux' } }];
|
||||
@@ -294,36 +271,26 @@ describe('IDEServer', () => {
|
||||
workspacePath: '/baz/qux',
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
ideName: 'VS Code',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
expectedLockFile,
|
||||
expectedContent2,
|
||||
);
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPpidPortFile,
|
||||
expectedContent2,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedLockFile, 0o600);
|
||||
});
|
||||
|
||||
it('should clear env vars and delete port file on stop', async () => {
|
||||
it('should clear env vars and delete lock file on stop', async () => {
|
||||
await ideServer.start(mockContext);
|
||||
const replaceMock = mockContext.environmentVariableCollection.replace;
|
||||
const port = getPortFromMock(replaceMock);
|
||||
const portFile = path.join('/tmp', `qwen-code-ide-server-${port}.json`);
|
||||
const ppidPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${process.ppid}.json`,
|
||||
);
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(portFile, expect.any(String));
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(ppidPortFile, expect.any(String));
|
||||
const lockFile = path.join('/home/test', '.qwen', 'ide', `${port}.lock`);
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(lockFile, expect.any(String));
|
||||
|
||||
await ideServer.stop();
|
||||
|
||||
expect(mockContext.environmentVariableCollection.clear).toHaveBeenCalled();
|
||||
expect(fs.unlink).toHaveBeenCalledWith(portFile);
|
||||
expect(fs.unlink).toHaveBeenCalledWith(ppidPortFile);
|
||||
expect(fs.unlink).toHaveBeenCalledWith(lockFile);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform !== 'win32')(
|
||||
@@ -344,30 +311,24 @@ describe('IDEServer', () => {
|
||||
);
|
||||
|
||||
const port = getPortFromMock(replaceMock);
|
||||
const expectedPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${port}.json`,
|
||||
);
|
||||
const expectedPpidPortFile = path.join(
|
||||
'/tmp',
|
||||
`qwen-code-ide-server-${process.ppid}.json`,
|
||||
const expectedLockFile = path.join(
|
||||
'/home/test',
|
||||
'.qwen',
|
||||
'ide',
|
||||
`${port}.lock`,
|
||||
);
|
||||
const expectedContent = JSON.stringify({
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: expectedWorkspacePaths,
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
ideName: 'VS Code',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
expectedLockFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedLockFile, 0o600);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -379,7 +340,7 @@ describe('IDEServer', () => {
|
||||
port = (ideServer as unknown as { port: number }).port;
|
||||
});
|
||||
|
||||
it('should allow request without auth token for backwards compatibility', async () => {
|
||||
it('should reject request without auth token', async () => {
|
||||
const response = await fetch(`http://localhost:${port}/mcp`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -390,7 +351,9 @@ describe('IDEServer', () => {
|
||||
id: 1,
|
||||
}),
|
||||
});
|
||||
expect(response.status).not.toBe(401);
|
||||
expect(response.status).toBe(401);
|
||||
const body = await response.text();
|
||||
expect(body).toBe('Unauthorized');
|
||||
});
|
||||
|
||||
it('should allow request with valid auth token', async () => {
|
||||
@@ -550,6 +513,7 @@ describe('IDEServer HTTP endpoints', () => {
|
||||
headers: {
|
||||
Host: `localhost:${port}`,
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer test-auth-token',
|
||||
},
|
||||
},
|
||||
JSON.stringify({ jsonrpc: '2.0', method: 'initialize' }),
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
IdeContextNotificationSchema,
|
||||
OpenDiffRequestSchema,
|
||||
} from '@qwen-code/qwen-code-core/src/ide/types.js';
|
||||
import { detectIdeFromEnv } from '@qwen-code/qwen-code-core/src/ide/detect-ide.js';
|
||||
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||
@@ -38,12 +39,24 @@ class CORSError extends Error {
|
||||
const MCP_SESSION_ID_HEADER = 'mcp-session-id';
|
||||
const IDE_SERVER_PORT_ENV_VAR = 'QWEN_CODE_IDE_SERVER_PORT';
|
||||
const IDE_WORKSPACE_PATH_ENV_VAR = 'QWEN_CODE_IDE_WORKSPACE_PATH';
|
||||
const QWEN_DIR = '.qwen';
|
||||
const IDE_DIR = 'ide';
|
||||
|
||||
async function getGlobalIdeDir(): Promise<string> {
|
||||
const homeDir = os.homedir();
|
||||
// Prefer home dir, but fall back to tmpdir if unavailable (matches core Storage behavior).
|
||||
const baseDir = homeDir
|
||||
? path.join(homeDir, QWEN_DIR)
|
||||
: path.join(os.tmpdir(), QWEN_DIR);
|
||||
const ideDir = path.join(baseDir, IDE_DIR);
|
||||
await fs.mkdir(ideDir, { recursive: true });
|
||||
return ideDir;
|
||||
}
|
||||
|
||||
interface WritePortAndWorkspaceArgs {
|
||||
context: vscode.ExtensionContext;
|
||||
port: number;
|
||||
portFile: string;
|
||||
ppidPortFile: string;
|
||||
lockFile: string;
|
||||
authToken: string;
|
||||
log: (message: string) => void;
|
||||
}
|
||||
@@ -51,8 +64,7 @@ interface WritePortAndWorkspaceArgs {
|
||||
async function writePortAndWorkspace({
|
||||
context,
|
||||
port,
|
||||
portFile,
|
||||
ppidPortFile,
|
||||
lockFile,
|
||||
authToken,
|
||||
log,
|
||||
}: WritePortAndWorkspaceArgs): Promise<void> {
|
||||
@@ -71,26 +83,24 @@ async function writePortAndWorkspace({
|
||||
workspacePath,
|
||||
);
|
||||
|
||||
const ideInfo = detectIdeFromEnv();
|
||||
const content = JSON.stringify({
|
||||
port,
|
||||
workspacePath,
|
||||
ppid: process.ppid,
|
||||
authToken,
|
||||
ideName: ideInfo.displayName,
|
||||
});
|
||||
|
||||
log(`Writing port file to: ${portFile}`);
|
||||
log(`Writing ppid port file to: ${ppidPortFile}`);
|
||||
log(`Writing IDE lock file to: ${lockFile}`);
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
fs.writeFile(portFile, content).then(() => fs.chmod(portFile, 0o600)),
|
||||
fs
|
||||
.writeFile(ppidPortFile, content)
|
||||
.then(() => fs.chmod(ppidPortFile, 0o600)),
|
||||
]);
|
||||
await fs.mkdir(path.dirname(lockFile), { recursive: true });
|
||||
await fs.writeFile(lockFile, content);
|
||||
await fs.chmod(lockFile, 0o600);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
log(`Failed to write port to file: ${message}`);
|
||||
log(`Failed to write IDE lock file: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +131,7 @@ export class IDEServer {
|
||||
private server: HTTPServer | undefined;
|
||||
private context: vscode.ExtensionContext | undefined;
|
||||
private log: (message: string) => void;
|
||||
private portFile: string | undefined;
|
||||
private ppidPortFile: string | undefined;
|
||||
private lockFile: string | undefined;
|
||||
private port: number | undefined;
|
||||
private authToken: string | undefined;
|
||||
private transports: { [sessionId: string]: StreamableHTTPServerTransport } =
|
||||
@@ -174,19 +183,24 @@ export class IDEServer {
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader) {
|
||||
const parts = authHeader.split(' ');
|
||||
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
||||
this.log('Malformed Authorization header. Rejecting request.');
|
||||
res.status(401).send('Unauthorized');
|
||||
return;
|
||||
}
|
||||
const token = parts[1];
|
||||
if (token !== this.authToken) {
|
||||
this.log('Invalid auth token provided. Rejecting request.');
|
||||
res.status(401).send('Unauthorized');
|
||||
return;
|
||||
}
|
||||
if (!authHeader) {
|
||||
this.log('Missing Authorization header. Rejecting request.');
|
||||
res.status(401).send('Unauthorized');
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = authHeader.split(' ');
|
||||
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
||||
this.log('Malformed Authorization header. Rejecting request.');
|
||||
res.status(401).send('Unauthorized');
|
||||
return;
|
||||
}
|
||||
|
||||
const token = parts[1];
|
||||
if (token !== this.authToken) {
|
||||
this.log('Invalid auth token provided. Rejecting request.');
|
||||
res.status(401).send('Unauthorized');
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
@@ -327,22 +341,21 @@ export class IDEServer {
|
||||
const address = (this.server as HTTPServer).address();
|
||||
if (address && typeof address !== 'string') {
|
||||
this.port = address.port;
|
||||
this.portFile = path.join(
|
||||
os.tmpdir(),
|
||||
`qwen-code-ide-server-${this.port}.json`,
|
||||
);
|
||||
this.ppidPortFile = path.join(
|
||||
os.tmpdir(),
|
||||
`qwen-code-ide-server-${process.ppid}.json`,
|
||||
);
|
||||
try {
|
||||
const ideDir = await getGlobalIdeDir();
|
||||
// Name the lock file by port to support multiple server instances.
|
||||
this.lockFile = path.join(ideDir, `${this.port}.lock`);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
this.log(`Failed to determine IDE lock directory: ${message}`);
|
||||
}
|
||||
this.log(`IDE server listening on http://127.0.0.1:${this.port}`);
|
||||
|
||||
if (this.authToken) {
|
||||
if (this.authToken && this.lockFile) {
|
||||
await writePortAndWorkspace({
|
||||
context,
|
||||
port: this.port,
|
||||
portFile: this.portFile,
|
||||
ppidPortFile: this.ppidPortFile,
|
||||
lockFile: this.lockFile,
|
||||
authToken: this.authToken,
|
||||
log: this.log,
|
||||
});
|
||||
@@ -371,15 +384,13 @@ export class IDEServer {
|
||||
this.context &&
|
||||
this.server &&
|
||||
this.port &&
|
||||
this.portFile &&
|
||||
this.ppidPortFile &&
|
||||
this.lockFile &&
|
||||
this.authToken
|
||||
) {
|
||||
await writePortAndWorkspace({
|
||||
context: this.context,
|
||||
port: this.port,
|
||||
portFile: this.portFile,
|
||||
ppidPortFile: this.ppidPortFile,
|
||||
lockFile: this.lockFile,
|
||||
authToken: this.authToken,
|
||||
log: this.log,
|
||||
});
|
||||
@@ -405,16 +416,9 @@ export class IDEServer {
|
||||
if (this.context) {
|
||||
this.context.environmentVariableCollection.clear();
|
||||
}
|
||||
if (this.portFile) {
|
||||
if (this.lockFile) {
|
||||
try {
|
||||
await fs.unlink(this.portFile);
|
||||
} catch (_err) {
|
||||
// Ignore errors if the file doesn't exist.
|
||||
}
|
||||
}
|
||||
if (this.ppidPortFile) {
|
||||
try {
|
||||
await fs.unlink(this.ppidPortFile);
|
||||
await fs.unlink(this.lockFile);
|
||||
} catch (_err) {
|
||||
// Ignore errors if the file doesn't exist.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user