mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-01-03 07:29:14 +00:00
Compare commits
38 Commits
v0.5.1-pre
...
feat/javas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
80bb2890df | ||
|
|
abd9ee2a7b | ||
|
|
b8df689e31 | ||
|
|
e610578ecc | ||
|
|
235159216e | ||
|
|
93b30cca29 | ||
|
|
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)
|
||||
|
||||
14
package-lock.json
generated
14
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/*"
|
||||
],
|
||||
@@ -17494,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",
|
||||
@@ -17618,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",
|
||||
@@ -17767,7 +17767,7 @@
|
||||
},
|
||||
"packages/sdk-typescript": {
|
||||
"name": "@qwen-code/sdk",
|
||||
"version": "0.1.1",
|
||||
"version": "0.6.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.4",
|
||||
@@ -20197,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": {
|
||||
@@ -20209,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",
|
||||
|
||||
@@ -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
|
||||
13
packages/sdk-java/.gitignore
vendored
Normal file
13
packages/sdk-java/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
||||
# Maven
|
||||
log/
|
||||
target/
|
||||
|
||||
170
packages/sdk-java/QWEN.md
Normal file
170
packages/sdk-java/QWEN.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# 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.
|
||||
|
||||
The project is structured as a Maven-based Java library with the following key characteristics:
|
||||
|
||||
- **Group ID**: com.alibaba
|
||||
- **Artifact ID**: qwencode-sdk-java
|
||||
- **Version**: 0.0.1
|
||||
- **Packaging**: JAR
|
||||
- **Java Version**: 1.8+ (source and target)
|
||||
|
||||
## Architecture
|
||||
|
||||
The SDK follows a layered architecture:
|
||||
|
||||
- **CLI Layer**: Provides the main entry point through `QwenCodeCli` class
|
||||
- **Session Layer**: Manages communication sessions with the Qwen Code CLI
|
||||
- **Transport Layer**: Handles communication between the SDK and CLI process
|
||||
- **Protocol Layer**: Defines data structures for communication
|
||||
- **Utils**: Common utilities for concurrent execution and timeout handling
|
||||
|
||||
## 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
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
## 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 two primary methods:
|
||||
|
||||
- `simpleQuery(String prompt)`: Synchronous method that returns a list of responses
|
||||
- `simpleQuery(String prompt, Consumer<String> messageConsumer)`: Asynchronous method that streams responses to a consumer
|
||||
|
||||
### 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.
|
||||
|
||||
## Usage Example
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import java.util.List;
|
||||
|
||||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
List<String> result = QwenCodeCli.simpleQuery("hello world");
|
||||
result.forEach(System.out::println);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── main/
|
||||
│ └── java/
|
||||
│ └── com/
|
||||
│ └── alibaba/
|
||||
│ └── qwen/
|
||||
│ └── code/
|
||||
│ └── cli/
|
||||
│ ├── QwenCodeCli.java
|
||||
│ ├── protocol/
|
||||
│ ├── session/
|
||||
│ ├── transport/
|
||||
│ └── utils/
|
||||
└── test/
|
||||
└── java/
|
||||
└── com/
|
||||
└── alibaba/
|
||||
└── qwen/
|
||||
└── code/
|
||||
└── cli/
|
||||
└── QwenCodeCliTest.java
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
- `pom.xml`: Maven build configuration and dependencies
|
||||
- `checkstyle.xml`: Code style and formatting rules
|
||||
- `.editorconfig`: Editor configuration settings
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0 - see [LICENSE](./LICENSE) for details.
|
||||
786
packages/sdk-java/README.md
Normal file
786
packages/sdk-java/README.md
Normal file
@@ -0,0 +1,786 @@
|
||||
# Qwen Code Java SDK
|
||||
|
||||
A minimum experimental Java SDK for programmatic access to Qwen Code functionality. This SDK provides a Java interface to interact with the Qwen Code CLI, allowing developers to integrate Qwen Code capabilities into their Java applications.
|
||||
|
||||
Feel free to submit a feature request/issue/PR.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the following dependency to your Maven `pom.xml`:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>qwencode-sdk-java</artifactId>
|
||||
<version>0.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Or if using Gradle, add to your `build.gradle`:
|
||||
|
||||
```gradle
|
||||
implementation 'com.alibaba:qwencode-sdk-java:0.0.1'
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Java >= 1.8
|
||||
- Maven >= 3.6.0 (for building from source)
|
||||
- Qwen Code CLI: The SDK communicates with the Qwen Code CLI executable. By default, the SDK looks for a `qwen` command in the system PATH.
|
||||
|
||||
## Quick Start
|
||||
|
||||
The simplest way to use the SDK is through the `QwenCodeCli.simpleQuery()` method:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import java.util.List;
|
||||
|
||||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
List<String> result = QwenCodeCli.simpleQuery("hello world");
|
||||
result.forEach(System.out::println);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage with streaming responses:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StreamingExample {
|
||||
public static void main(String[] args) {
|
||||
QwenCodeCli.simpleQuery("hello world", (String message) -> {
|
||||
System.out.println("Received: " + message);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For session-based usage with custom event handling:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SessionExample {
|
||||
public static void main(String[] args) {
|
||||
try (Session session = QwenCodeCli.newSession()) {
|
||||
SessionEventSimpleConsumers eventConsumers = new SessionEventSimpleConsumers() {
|
||||
@Override
|
||||
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
|
||||
String message = assistantMessage.getMessage().getContent().stream()
|
||||
.findFirst()
|
||||
.map(content -> content.getText())
|
||||
.orElse("");
|
||||
System.out.println("Assistant: " + message);
|
||||
}
|
||||
}.setDefaultEventTimeout(new Timeout(60L, TimeUnit.SECONDS));
|
||||
|
||||
session.sendPrompt("hello world", eventConsumers);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The Qwen Code Java SDK follows a layered architecture that abstracts the communication with the Qwen Code CLI:
|
||||
|
||||
### 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
|
||||
|
||||
### Core Classes and Their Relationships
|
||||
|
||||
- `QwenCodeCli`: The main entry point that provides static methods (`simpleQuery`) which internally create and manage `Session` instances
|
||||
- `Session`: Manages the lifecycle of a communication session with the CLI, including initialization, prompt sending, and cleanup
|
||||
- `Transport`: Abstracts the communication mechanism (currently implemented by `ProcessTransport`)
|
||||
- `ProcessTransport`: Implementation that communicates with the CLI via process execution, using `TransportOptions` for configuration
|
||||
- `TransportOptions`: Configuration class that defines how the transport layer should interact with the CLI (path to executable, working directory, model, permission mode, etc.)
|
||||
- `SessionEventSimpleConsumers`: Event handler interface for processing responses from the CLI, allowing custom handling of assistant messages and other events
|
||||
- `PermissionMode`: Enum that defines different permission modes for controlling tool execution (default, plan, auto-edit, yolo)
|
||||
|
||||
The architecture allows for both simple usage through static methods in `QwenCodeCli` and more advanced usage through direct `Session` management with custom event handlers and transport options.
|
||||
|
||||
## Usage
|
||||
|
||||
### Session Event Consumers
|
||||
|
||||
The SDK allows you to customize how events from the CLI are handled using event consumers. 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)
|
||||
- `onAssistantMessageIncludePartial`: Handles assistant messages including partial content (specific to SessionEventSimpleConsumers, called by both onAssistantMessage and onPartialAssistantMessage) (receives Session, List<AssistantContent>, and AssistantMessageOutputType)
|
||||
|
||||
Event processing is subject to the timeout settings configured in `TransportOptions` and `SessionEventConsumers`. For detailed timeout configuration options, see the "Timeout" section above.
|
||||
|
||||
Example of custom event handling:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers;
|
||||
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.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.control.CLIControlPermissionRequest;
|
||||
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.data.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers.AssistantMessageOutputType;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CustomEventHandlingExample {
|
||||
public static void main(String[] args) {
|
||||
Session session = QwenCodeCli.newSession();
|
||||
SessionEventSimpleConsumers eventConsumers = new SessionEventSimpleConsumers() {
|
||||
@Override
|
||||
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
|
||||
String message = assistantMessage.getMessage().getContent().stream()
|
||||
.findFirst()
|
||||
.map(content -> content.getText())
|
||||
.orElse("");
|
||||
System.out.println("Assistant: " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) {
|
||||
System.out.println("Partial assistant message: " + partialAssistantMessage);
|
||||
}
|
||||
|
||||
public void onAssistantMessageIncludePartial(Session session, List<AssistantContent> assistantContents,
|
||||
AssistantMessageOutputType assistantMessageOutputType) {
|
||||
System.out.println("Assistant content (type: " + assistantMessageOutputType + "): " + assistantContents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemMessage(Session session, SDKSystemMessage systemMessage) {
|
||||
System.out.println("System: " + systemMessage.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResultMessage(Session session, SDKResultMessage resultMessage) {
|
||||
System.out.println("Result: " + resultMessage.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserMessage(Session session, SDKUserMessage userMessage) {
|
||||
System.out.println("User: " + userMessage.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOtherMessage(Session session, String message) {
|
||||
System.out.println("Other: " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse) {
|
||||
System.out.println("Control response: " + cliControlResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CLIControlResponse<?> onControlRequest(Session session, CLIControlRequest<?> cliControlRequest) {
|
||||
System.out.println("Control request: " + cliControlRequest);
|
||||
return new CLIControlResponse<>(); // Return appropriate response
|
||||
}
|
||||
|
||||
@Override
|
||||
public Behavior onPermissionRequest(Session session, CLIControlRequest<CLIControlPermissionRequest> permissionRequest) {
|
||||
System.out.println("Permission request: " + permissionRequest.getRequest().getInput());
|
||||
return new com.alibaba.qwen.code.cli.protocol.data.behavior.Allow()
|
||||
.setUpdatedInput(permissionRequest.getRequest().getInput()); // Allow by default
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onAssistantMessageTimeout(Session session) {
|
||||
return new Timeout(90L, TimeUnit.SECONDS); // Timeout for processing assistant messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onSystemMessageTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing system messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onResultMessageTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing result messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onPartialAssistantMessageTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing partial assistant messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onUserMessageTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing user messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onOtherMessageTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing other messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onControlResponseTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing control responses
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onControlRequestTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing control requests
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onPermissionRequestTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing permission requests
|
||||
}
|
||||
}.setDefaultEventTimeout(new Timeout(60L, TimeUnit.SECONDS)); // Default timeout for all events
|
||||
|
||||
session.sendPrompt("Example prompt", eventConsumers);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
To set a permission mode:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
|
||||
public class PermissionModeExample {
|
||||
public static void main(String[] args) {
|
||||
Session session = QwenCodeCli.newSession(new TransportOptions().setPermissionMode(PermissionMode.YOLO));
|
||||
session.setPermissionMode(PermissionMode.PLAN);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Session Control
|
||||
|
||||
The SDK provides fine-grained control over session lifecycle and behavior:
|
||||
|
||||
- **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
|
||||
|
||||
Example of session control:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
import java.util.List;
|
||||
|
||||
public class SessionControlExample {
|
||||
public static void main(String[] args) {
|
||||
TransportOptions options = new TransportOptions()
|
||||
.setModel("qwen-max")
|
||||
.setPermissionMode(PermissionMode.AUTO_EDIT);
|
||||
|
||||
try (Session session = QwenCodeCli.newSession(options)) {
|
||||
// Use the session with default event consumers
|
||||
List<String> result = session.sendPrompt("Explain how to use the SDK", new SessionEventSimpleConsumers());
|
||||
result.forEach(System.out::println);
|
||||
} // Session automatically closes when exiting try-with-resources
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Interrupt Function
|
||||
|
||||
The `interrupt()` function allows you to interrupt a currently running prompt. This is useful when you need to stop a long-running operation without closing the entire session:
|
||||
|
||||
- **Method signature**: `public Optional<Boolean> interrupt() throws SessionControlException`
|
||||
- **Purpose**: Interrupts the current prompt processing without closing the session
|
||||
- **Return value**: An `Optional<Boolean>` that indicates whether the interrupt was successful (true if successful, empty if the interrupt was sent asynchronously)
|
||||
|
||||
Example of interrupting a running prompt:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage;
|
||||
import com.alibaba.qwen.code.cli.session.exception.SessionControlException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class InterruptExample {
|
||||
public static void main(String[] args) {
|
||||
try (Session session = QwenCodeCli.newSession()) {
|
||||
session.sendPrompt("Analyze this large codebase...", new SessionEventSimpleConsumers() {
|
||||
@Override
|
||||
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
|
||||
System.out.println("Received: " + assistantMessage.getMessage().getContent().stream()
|
||||
.findFirst()
|
||||
.map(content -> content.getText())
|
||||
.orElse(""));
|
||||
|
||||
// Interrupt the session after receiving the first message
|
||||
try {
|
||||
Optional<Boolean> interruptResult = session.interrupt();
|
||||
System.out.println(interruptResult.map(s -> s ? "Interrupt successful" : "Interrupt error")
|
||||
.orElse("Interrupt unknown"));
|
||||
} catch (SessionControlException e) {
|
||||
System.err.println("Interrupt error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Set Model Function
|
||||
|
||||
The `setModel()` function allows you to dynamically change the AI model during an active session. This is useful when you want to switch between different models (e.g., from a faster model for simple queries to a more powerful model for complex analysis) without creating a new session:
|
||||
|
||||
- **Method signature**: `public Optional<Boolean> setModel(String modelName) throws SessionControlException`
|
||||
- **Purpose**: Changes the AI model being used for the current and subsequent prompts in the session
|
||||
- **Parameters**: `modelName` - the name of the model to switch to (e.g., "qwen-max", "qwen-plus", etc.)
|
||||
- **Return value**: An `Optional<Boolean>` that indicates whether the model change was successful (true if successful, empty if the request was sent asynchronously)
|
||||
|
||||
Example of changing the model during a session:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SetModelExample {
|
||||
public static void main(String[] args) {
|
||||
try (Session session = QwenCodeCli.newSession()) {
|
||||
// Switch to a specific model
|
||||
Optional<Boolean> modelChangeResult = session.setModel("qwen3-coder-flash");
|
||||
System.out.println(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");
|
||||
System.out.println(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Set Permission Mode Function
|
||||
|
||||
The `setPermissionMode()` function allows you to dynamically change the permission mode during an active session. This is useful when you want to adjust the level of access granted to tools (e.g., switching from a restrictive mode to allow more operations) without creating a new session:
|
||||
|
||||
- **Method signature**: `public Optional<Boolean> setPermissionMode(PermissionMode permissionMode) throws SessionControlException`
|
||||
- **Purpose**: Changes the permission mode governing tool execution for the current and subsequent prompts in the session
|
||||
- **Parameters**: `permissionMode` - the permission mode to switch to (e.g., `PermissionMode.DEFAULT`, `PermissionMode.PLAN`, `PermissionMode.AUTO_EDIT`, `PermissionMode.YOLO`)
|
||||
- **Return value**: An `Optional<Boolean>` that indicates whether the permission mode change was successful (true if successful, empty if the request was sent asynchronously)
|
||||
|
||||
Example of changing the permission mode during a session:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SetPermissionModeExample {
|
||||
public static void main(String[] args) {
|
||||
try (Session session = QwenCodeCli.newSession()) {
|
||||
// Switch to a permissive mode
|
||||
Optional<Boolean> permissionChangeResult = session.setPermissionMode(PermissionMode.YOLO);
|
||||
System.out.println(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);
|
||||
System.out.println(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Timeout Configuration
|
||||
|
||||
The timeout configuration allows you to control how long the SDK waits for responses from the CLI before timing out. There are two levels of timeout configuration:
|
||||
|
||||
- **Transport-level timeouts**: Configured via `TransportOptions`
|
||||
- `turnTimeout`: Time to wait for a complete turn of conversation (default: 60 seconds)
|
||||
- `messageTimeout`: Time to wait for individual messages within a turn (default: 60 seconds)
|
||||
|
||||
- **Event-level timeouts**: Configured via `SessionEventConsumers` interface with callback methods for specific message types:
|
||||
- `onSystemMessageTimeout`: Timeout for processing system messages
|
||||
- `onResultMessageTimeout`: Timeout for processing result messages
|
||||
- `onAssistantMessageTimeout`: Timeout for processing assistant messages
|
||||
- `onPartialAssistantMessageTimeout`: Timeout for processing partial assistant messages
|
||||
- `onUserMessageTimeout`: Timeout for processing user messages
|
||||
- `onOtherMessageTimeout`: Timeout for processing other types of messages
|
||||
- `onControlResponseTimeout`: Timeout for processing control responses
|
||||
- `onControlRequestTimeout`: Timeout for processing control requests
|
||||
- `onPermissionRequestTimeout`: Timeout for processing permission requests
|
||||
|
||||
To customize timeout settings:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TimeoutConfigurationExample {
|
||||
public static void main(String[] args) {
|
||||
// Configure transport-level timeouts
|
||||
TransportOptions options = new TransportOptions()
|
||||
.setTurnTimeout(new Timeout(120L, TimeUnit.SECONDS)) // Timeout for a complete turn of conversation
|
||||
.setMessageTimeout(new Timeout(90L, TimeUnit.SECONDS)); // Timeout for individual messages within a turn
|
||||
|
||||
Session session = QwenCodeCli.newSession(options);
|
||||
|
||||
// Configure event-level timeouts using SessionEventConsumers
|
||||
SessionEventConsumers eventConsumers = new SessionEventSimpleConsumers() {
|
||||
@Override
|
||||
public Timeout onSystemMessageTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing system messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onResultMessageTimeout(Session session) {
|
||||
return new Timeout(60L, TimeUnit.SECONDS); // Timeout for processing result messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onAssistantMessageTimeout(Session session) {
|
||||
return new Timeout(90L, TimeUnit.SECONDS); // Timeout for processing assistant messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onControlResponseTimeout(Session session) {
|
||||
return new Timeout(45L, TimeUnit.SECONDS); // Timeout for processing control responses
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onPermissionRequestTimeout(Session session) {
|
||||
return new Timeout(30L, TimeUnit.SECONDS); // Timeout for processing permission requests
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onOtherMessageTimeout(Session session) {
|
||||
return new Timeout(35L, TimeUnit.SECONDS); // Timeout for processing other messages
|
||||
}
|
||||
}.setDefaultEventTimeout(new Timeout(90L, TimeUnit.SECONDS)); // Default timeout for all events
|
||||
session.sendPrompt("hello world", eventConsumers);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Thread Pool Configuration
|
||||
|
||||
The SDK uses a thread pool for managing concurrent operations. The default thread pool configuration is defined in the `ThreadPoolConfig` class:
|
||||
|
||||
- **Core Pool Size**: 10 threads
|
||||
- **Maximum Pool Size**: 30 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 (executes the task on the calling thread when the pool is full)
|
||||
|
||||
The thread pool can be customized in two ways:
|
||||
|
||||
1. **Using a custom supplier**: Provide a custom `Supplier<ThreadPoolExecutor>` through the `ThreadPoolConfig.setExecutorSupplier()` method. If no custom supplier is provided, or if the supplier throws an exception, the SDK will fall back to the default thread pool configuration.
|
||||
|
||||
2. **Modifying properties after getting the default executor**: You can retrieve the default executor using `ThreadPoolConfig.getDefaultExecutor()` and then modify its properties such as core pool size, maximum pool size, and keep-alive time.
|
||||
|
||||
Example of custom thread pool configuration using a supplier:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.utils.ThreadPoolConfig;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ThreadPoolConfigurationExample {
|
||||
public static void main(String[] args) {
|
||||
// Set a custom thread pool supplier
|
||||
ThreadPoolConfig.setExecutorSupplier(new Supplier<ThreadPoolExecutor>() {
|
||||
@Override
|
||||
public ThreadPoolExecutor get() {
|
||||
return (ThreadPoolExecutor) Executors.newFixedThreadPool(20);
|
||||
}
|
||||
});
|
||||
|
||||
// The SDK will now use the custom thread pool for all operations
|
||||
Session session = QwenCodeCli.newSession();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example of modifying properties after getting the default executor:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.utils.ThreadPoolConfig;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ModifyThreadPoolExample {
|
||||
public static void main(String[] args) {
|
||||
// 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);
|
||||
|
||||
// The SDK will now use the modified executor for all operations
|
||||
Session session = QwenCodeCli.newSession();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that when modifying the default executor directly, you're changing the properties of the shared static instance that will affect all subsequent operations in the application. If you need different configurations for different parts of your application, using the supplier approach is recommended.
|
||||
|
||||
### Transport Options
|
||||
|
||||
The `TransportOptions` class allows you to configure how the SDK communicates with the Qwen Code CLI. Below are all the available options with their descriptions:
|
||||
|
||||
- **`pathToQwenExecutable`**: Specifies the path to the Qwen Code CLI executable. By default, the SDK looks for a `qwen` command in the system PATH.
|
||||
- Type: `String`
|
||||
- Example: `new TransportOptions().setPathToQwenExecutable("/usr/local/bin/qwen")`
|
||||
|
||||
- **`cwd`**: Sets the working directory for the CLI process. This affects where the CLI operates and where relative paths are resolved from.
|
||||
- Type: `String`
|
||||
- Example: `new TransportOptions().setCwd("/path/to/project")`
|
||||
|
||||
- **`model`**: Specifies the AI model to use for the session (e.g., "qwen-max", "qwen-plus", "qwen3-coder-flash", etc.).
|
||||
- Type: `String`
|
||||
- Example: `new TransportOptions().setModel("qwen3-coder-flash")`
|
||||
|
||||
- **`permissionMode`**: Sets the permission mode that controls tool execution. Available modes are:
|
||||
- `PermissionMode.DEFAULT`: Write tools are denied unless approved via `canUseTool` callback or in `allowedTools`. Read-only tools execute without confirmation.
|
||||
- `PermissionMode.PLAN`: Blocks all write tools, instructing AI to present a plan first.
|
||||
- `PermissionMode.AUTO_EDIT`: Auto-approve edit tools (edit, write_file) while other tools require confirmation.
|
||||
- `PermissionMode.YOLO`: All tools execute automatically without confirmation.
|
||||
- Type: `PermissionMode`
|
||||
- Example: `new TransportOptions().setPermissionMode(PermissionMode.YOLO)`
|
||||
|
||||
- **`env`**: A map of environment variables to pass to the CLI process.
|
||||
- Type: `Map<String, String>`
|
||||
- Example: `new TransportOptions().setEnv(Map.of("ENV_VAR", "value"))`
|
||||
|
||||
- **`maxSessionTurns`**: Limits the number of conversation turns in a session.
|
||||
- Type: `Integer`
|
||||
- Example: `new TransportOptions().setMaxSessionTurns(10)`
|
||||
|
||||
- **`coreTools`**: Specifies a list of core tools that should be available to the AI.
|
||||
- Type: `List<String>`
|
||||
- Example: `new TransportOptions().setCoreTools(List.of("read_file", "write_file"))`
|
||||
|
||||
- **`excludeTools`**: Specifies a list of tools to exclude from being available to the AI.
|
||||
- Type: `List<String>`
|
||||
- Example: `new TransportOptions().setExcludeTools(List.of("shell"))`
|
||||
|
||||
- **`allowedTools`**: Specifies a list of tools that are pre-approved for use without additional confirmation.
|
||||
- Type: `List<String>`
|
||||
- Example: `new TransportOptions().setAllowedTools(List.of("read_file", "list_directory"))`
|
||||
|
||||
- **`authType`**: Specifies the authentication type to use for the session.
|
||||
- Type: `String`
|
||||
- Example: `new TransportOptions().setAuthType("bearer")`
|
||||
|
||||
- **`includePartialMessages`**: When true, enables receiving partial messages during streaming responses.
|
||||
- Type: `Boolean`
|
||||
- Example: `new TransportOptions().setIncludePartialMessages(true)`
|
||||
|
||||
- **`skillsEnable`**: Enables or disables skills functionality for the session.
|
||||
- Type: `Boolean`
|
||||
- Example: `new TransportOptions().setSkillsEnable(true)`
|
||||
|
||||
- **`turnTimeout`**: Sets the timeout for a complete turn of conversation (default: 60 seconds).
|
||||
- Type: `Timeout`
|
||||
- Example: `new TransportOptions().setTurnTimeout(new Timeout(120L, TimeUnit.SECONDS))`
|
||||
|
||||
- **`messageTimeout`**: Sets the timeout for individual messages within a turn (default: 60 seconds).
|
||||
- Type: `Timeout`
|
||||
- Example: `new TransportOptions().setMessageTimeout(new Timeout(90L, TimeUnit.SECONDS))`
|
||||
|
||||
- **`resumeSessionId`**: Specifies the ID of a previous session to resume.
|
||||
- Type: `String`
|
||||
- Example: `new TransportOptions().setResumeSessionId("session-12345")`
|
||||
|
||||
- **`otherOptions`**: Allows passing additional command-line options directly to the CLI.
|
||||
- Type: `List<String>`
|
||||
- Example: `new TransportOptions().setOtherOptions(List.of("--verbose", "--no-cache"))`
|
||||
|
||||
Example of using TransportOptions:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.transport.TransportOptions;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TransportOptionsExample {
|
||||
public static void main(String[] args) {
|
||||
TransportOptions options = new TransportOptions()
|
||||
.setModel("qwen3-coder-flash")
|
||||
.setPermissionMode(PermissionMode.AUTO_EDIT)
|
||||
.setCwd("/path/to/working/directory")
|
||||
.setEnv(Map.of("CUSTOM_VAR", "value"))
|
||||
.setIncludePartialMessages(true)
|
||||
.setTurnTimeout(new Timeout(120L, TimeUnit.SECONDS))
|
||||
.setMessageTimeout(new Timeout(90L, TimeUnit.SECONDS))
|
||||
.setAllowedTools(List.of("read_file", "write_file", "list_directory"));
|
||||
|
||||
try (Session session = QwenCodeCli.newSession(options)) {
|
||||
// Use the session with custom options
|
||||
List<String> result = session.sendPrompt("Analyze the current project", new SessionEventSimpleConsumers());
|
||||
result.forEach(System.out::println);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
Example of comprehensive error handling:
|
||||
|
||||
```java
|
||||
import com.alibaba.qwen.code.cli.QwenCodeCli;
|
||||
import com.alibaba.qwen.code.cli.session.Session;
|
||||
import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.exception.SessionControlException;
|
||||
import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException;
|
||||
import java.util.List;
|
||||
|
||||
public class ErrorHandlingExample {
|
||||
public static void main(String[] args) {
|
||||
try (Session session = QwenCodeCli.newSession()) {
|
||||
try {
|
||||
List<String> result = session.sendPrompt("Process this request", new SessionEventSimpleConsumers());
|
||||
result.forEach(System.out::println);
|
||||
} catch (SessionSendPromptException e) {
|
||||
System.err.println("Error sending prompt: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (SessionControlException e) {
|
||||
System.err.println("Error controlling session: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Unexpected error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
## 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>
|
||||
209
packages/sdk-java/pom.xml
Normal file
209
packages/sdk-java/pom.xml
Normal file
@@ -0,0 +1,209 @@
|
||||
<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</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>
|
||||
</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-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile-examples</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<includes>
|
||||
<include>com/alibaba/qwen/code/example/**/*.java</include>
|
||||
</includes>
|
||||
<compileSourceRoots>
|
||||
<compileSourceRoot>${project.basedir}/src/example/java</compileSourceRoot>
|
||||
</compileSourceRoots>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<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.9.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>central</publishingServerId>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.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>2.9.1</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>1.5</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>
|
||||
</distributionManagement>
|
||||
</project>
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.alibaba.qwen.code.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
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.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;
|
||||
|
||||
public class QwenCodeCli {
|
||||
private static final Logger log = LoggerFactory.getLogger(QwenCodeCli.class);
|
||||
|
||||
public static List<String> simpleQuery(String prompt) {
|
||||
final List<String> response = new ArrayList<>();
|
||||
MyConcurrentUtils.runAndWait(() -> simpleQuery(prompt, response::add), Timeout.TIMEOUT_30_MINUTES);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static void simpleQuery(String prompt, Consumer<String> messageConsumer) {
|
||||
Session session = newSession(new TransportOptions());
|
||||
try {
|
||||
session.sendPrompt(prompt, new SessionEventSimpleConsumers() {
|
||||
@Override
|
||||
public void onAssistantMessageIncludePartial(Session session, List<AssistantContent> assistantContents, AssistantMessageOutputType assistantMessageOutputType) {
|
||||
messageConsumer.accept(assistantContents.stream()
|
||||
.map(AssistantContent::getContent)
|
||||
.map(content -> {
|
||||
if (content instanceof String) {
|
||||
return (String) content;
|
||||
} else {
|
||||
return JSON.toJSONString(content);
|
||||
}
|
||||
}).collect(Collectors.joining()));
|
||||
}
|
||||
}.setDefaultPermissionOperation(Operation.allow));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("sendPrompt error!", e);
|
||||
} finally {
|
||||
try {
|
||||
session.close();
|
||||
} catch (Exception e) {
|
||||
log.error("close session error!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Session newSession() {
|
||||
return newSession(new TransportOptions());
|
||||
}
|
||||
|
||||
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,6 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
public interface AssistantContent {
|
||||
String getType();
|
||||
Object getContent();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
public class CLIPermissionDenial {
|
||||
@JSONField(name = "tool_name")
|
||||
private String toolName;
|
||||
|
||||
@JSONField(name = "tool_use_id")
|
||||
private String toolUseId;
|
||||
|
||||
@JSONField(name = "tool_input")
|
||||
private Object toolInput;
|
||||
|
||||
public String getToolName() {
|
||||
return toolName;
|
||||
}
|
||||
|
||||
public void setToolName(String toolName) {
|
||||
this.toolName = toolName;
|
||||
}
|
||||
|
||||
public String getToolUseId() {
|
||||
return toolUseId;
|
||||
}
|
||||
|
||||
public void setToolUseId(String toolUseId) {
|
||||
this.toolUseId = toolUseId;
|
||||
}
|
||||
|
||||
public Object getToolInput() {
|
||||
return toolInput;
|
||||
}
|
||||
|
||||
public void setToolInput(Object toolInput) {
|
||||
this.toolInput = toolInput;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
public class Capabilities {
|
||||
@JSONField(name = "can_handle_can_use_tool")
|
||||
boolean canHandleCanUseTool;
|
||||
|
||||
@JSONField(name = "can_handle_hook_callback")
|
||||
boolean canHandleHookCallback;
|
||||
|
||||
@JSONField(name = "can_set_permission_mode")
|
||||
boolean canSetPermissionMode;
|
||||
|
||||
@JSONField(name = "can_set_model")
|
||||
boolean canSetModel;
|
||||
|
||||
@JSONField(name = "can_handle_mcp_message")
|
||||
boolean canHandleMcpMessage;
|
||||
|
||||
public boolean isCanHandleCanUseTool() {
|
||||
return canHandleCanUseTool;
|
||||
}
|
||||
|
||||
public void setCanHandleCanUseTool(boolean canHandleCanUseTool) {
|
||||
this.canHandleCanUseTool = canHandleCanUseTool;
|
||||
}
|
||||
|
||||
public boolean isCanHandleHookCallback() {
|
||||
return canHandleHookCallback;
|
||||
}
|
||||
|
||||
public void setCanHandleHookCallback(boolean canHandleHookCallback) {
|
||||
this.canHandleHookCallback = canHandleHookCallback;
|
||||
}
|
||||
|
||||
public boolean isCanSetPermissionMode() {
|
||||
return canSetPermissionMode;
|
||||
}
|
||||
|
||||
public void setCanSetPermissionMode(boolean canSetPermissionMode) {
|
||||
this.canSetPermissionMode = canSetPermissionMode;
|
||||
}
|
||||
|
||||
public boolean isCanSetModel() {
|
||||
return canSetModel;
|
||||
}
|
||||
|
||||
public void setCanSetModel(boolean canSetModel) {
|
||||
this.canSetModel = canSetModel;
|
||||
}
|
||||
|
||||
public boolean isCanHandleMcpMessage() {
|
||||
return canHandleMcpMessage;
|
||||
}
|
||||
|
||||
public void setCanHandleMcpMessage(boolean canHandleMcpMessage) {
|
||||
this.canHandleMcpMessage = canHandleMcpMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
public class ExtendedUsage extends Usage {
|
||||
@JSONField(name = "server_tool_use")
|
||||
private ServerToolUse serverToolUse;
|
||||
|
||||
@JSONField(name = "service_tier")
|
||||
private String serviceTier;
|
||||
|
||||
@JSONField(name = "cache_creation")
|
||||
private CacheCreation cacheCreation;
|
||||
|
||||
public ServerToolUse getServerToolUse() {
|
||||
return serverToolUse;
|
||||
}
|
||||
|
||||
public void setServerToolUse(ServerToolUse serverToolUse) {
|
||||
this.serverToolUse = serverToolUse;
|
||||
}
|
||||
|
||||
public String getServiceTier() {
|
||||
return serviceTier;
|
||||
}
|
||||
|
||||
public void setServiceTier(String serviceTier) {
|
||||
this.serviceTier = serviceTier;
|
||||
}
|
||||
|
||||
public CacheCreation getCacheCreation() {
|
||||
return cacheCreation;
|
||||
}
|
||||
|
||||
public void setCacheCreation(CacheCreation cacheCreation) {
|
||||
this.cacheCreation = cacheCreation;
|
||||
}
|
||||
|
||||
public static class ServerToolUse {
|
||||
@JSONField(name = "web_search_requests")
|
||||
private int webSearchRequests;
|
||||
}
|
||||
|
||||
public static class CacheCreation {
|
||||
@JSONField(name = "ephemeral_1h_input_tokens")
|
||||
private int ephemeral1hInputTokens;
|
||||
|
||||
@JSONField(name = "ephemeral_5m_input_tokens")
|
||||
private int ephemeral5mInputTokens;
|
||||
|
||||
public int getEphemeral1hInputTokens() {
|
||||
return ephemeral1hInputTokens;
|
||||
}
|
||||
|
||||
public void setEphemeral1hInputTokens(int ephemeral1hInputTokens) {
|
||||
this.ephemeral1hInputTokens = ephemeral1hInputTokens;
|
||||
}
|
||||
|
||||
public int getEphemeral5mInputTokens() {
|
||||
return ephemeral5mInputTokens;
|
||||
}
|
||||
|
||||
public void setEphemeral5mInputTokens(int ephemeral5mInputTokens) {
|
||||
this.ephemeral5mInputTokens = ephemeral5mInputTokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
public class InitializeConfig {
|
||||
String hooks;
|
||||
String sdkMcpServers;
|
||||
String mcpServers;
|
||||
String agents;
|
||||
|
||||
public String getHooks() {
|
||||
return hooks;
|
||||
}
|
||||
|
||||
public void setHooks(String hooks) {
|
||||
this.hooks = hooks;
|
||||
}
|
||||
|
||||
public String getSdkMcpServers() {
|
||||
return sdkMcpServers;
|
||||
}
|
||||
|
||||
public void setSdkMcpServers(String sdkMcpServers) {
|
||||
this.sdkMcpServers = sdkMcpServers;
|
||||
}
|
||||
|
||||
public String getMcpServers() {
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
public void setMcpServers(String mcpServers) {
|
||||
this.mcpServers = mcpServers;
|
||||
}
|
||||
|
||||
public String getAgents() {
|
||||
return agents;
|
||||
}
|
||||
|
||||
public void setAgents(String agents) {
|
||||
this.agents = agents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
public class ModelUsage {
|
||||
private int inputTokens;
|
||||
private int outputTokens;
|
||||
private int cacheReadInputTokens;
|
||||
private int cacheCreationInputTokens;
|
||||
private int webSearchRequests;
|
||||
private int contextWindow;
|
||||
|
||||
public int getInputTokens() {
|
||||
return inputTokens;
|
||||
}
|
||||
|
||||
public void setInputTokens(int inputTokens) {
|
||||
this.inputTokens = inputTokens;
|
||||
}
|
||||
|
||||
public int getOutputTokens() {
|
||||
return outputTokens;
|
||||
}
|
||||
|
||||
public void setOutputTokens(int outputTokens) {
|
||||
this.outputTokens = outputTokens;
|
||||
}
|
||||
|
||||
public int getCacheReadInputTokens() {
|
||||
return cacheReadInputTokens;
|
||||
}
|
||||
|
||||
public void setCacheReadInputTokens(int cacheReadInputTokens) {
|
||||
this.cacheReadInputTokens = cacheReadInputTokens;
|
||||
}
|
||||
|
||||
public int getCacheCreationInputTokens() {
|
||||
return cacheCreationInputTokens;
|
||||
}
|
||||
|
||||
public void setCacheCreationInputTokens(int cacheCreationInputTokens) {
|
||||
this.cacheCreationInputTokens = cacheCreationInputTokens;
|
||||
}
|
||||
|
||||
public int getWebSearchRequests() {
|
||||
return webSearchRequests;
|
||||
}
|
||||
|
||||
public void setWebSearchRequests(int webSearchRequests) {
|
||||
this.webSearchRequests = webSearchRequests;
|
||||
}
|
||||
|
||||
public int getContextWindow() {
|
||||
return contextWindow;
|
||||
}
|
||||
|
||||
public void setContextWindow(int contextWindow) {
|
||||
this.contextWindow = contextWindow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
public enum PermissionMode {
|
||||
DEFAULT("default"),
|
||||
PLAN("plan"),
|
||||
AUTO_EDIT("auto-edit"),
|
||||
YOLO("yolo");
|
||||
|
||||
private final String value;
|
||||
|
||||
PermissionMode(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
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,56 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
public class Usage {
|
||||
@JSONField(name = "input_tokens")
|
||||
private Integer inputTokens;
|
||||
@JSONField(name = "output_tokens")
|
||||
private Integer outputTokens;
|
||||
@JSONField(name = "cache_creation_input_tokens")
|
||||
private Integer cacheCreationInputTokens;
|
||||
@JSONField(name = "cache_read_input_tokens")
|
||||
private Integer cacheReadInputTokens;
|
||||
@JSONField(name = "total_tokens")
|
||||
private Integer totalTokens;
|
||||
|
||||
public Integer getInputTokens() {
|
||||
return inputTokens;
|
||||
}
|
||||
|
||||
public void setInputTokens(Integer inputTokens) {
|
||||
this.inputTokens = inputTokens;
|
||||
}
|
||||
|
||||
public Integer getOutputTokens() {
|
||||
return outputTokens;
|
||||
}
|
||||
|
||||
public void setOutputTokens(Integer outputTokens) {
|
||||
this.outputTokens = outputTokens;
|
||||
}
|
||||
|
||||
public Integer getCacheCreationInputTokens() {
|
||||
return cacheCreationInputTokens;
|
||||
}
|
||||
|
||||
public void setCacheCreationInputTokens(Integer cacheCreationInputTokens) {
|
||||
this.cacheCreationInputTokens = cacheCreationInputTokens;
|
||||
}
|
||||
|
||||
public Integer getCacheReadInputTokens() {
|
||||
return cacheReadInputTokens;
|
||||
}
|
||||
|
||||
public void setCacheReadInputTokens(Integer cacheReadInputTokens) {
|
||||
this.cacheReadInputTokens = cacheReadInputTokens;
|
||||
}
|
||||
|
||||
public Integer getTotalTokens() {
|
||||
return totalTokens;
|
||||
}
|
||||
|
||||
public void setTotalTokens(Integer totalTokens) {
|
||||
this.totalTokens = totalTokens;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data.behavior;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "operation", typeName = "allow")
|
||||
public class Allow extends Behavior {
|
||||
public Allow() {
|
||||
super();
|
||||
this.behavior = Operation.allow;
|
||||
}
|
||||
Map<String, Object> updatedInput;
|
||||
|
||||
public Map<String, Object> getUpdatedInput() {
|
||||
return updatedInput;
|
||||
}
|
||||
|
||||
public Allow setUpdatedInput(Map<String, Object> updatedInput) {
|
||||
this.updatedInput = updatedInput;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data.behavior;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "operation", typeName = "Behavior", seeAlso = {Allow.class, Deny.class})
|
||||
public class Behavior {
|
||||
Operation behavior;
|
||||
|
||||
public Operation getBehavior() {
|
||||
return behavior;
|
||||
}
|
||||
|
||||
public void setBehavior(Operation behavior) {
|
||||
this.behavior = behavior;
|
||||
}
|
||||
|
||||
public enum Operation {
|
||||
allow,
|
||||
deny
|
||||
}
|
||||
|
||||
public static Behavior defaultBehavior() {
|
||||
return new Deny().setMessage("Default Behavior Permission denied");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.data.behavior;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "operation", typeName = "deny")
|
||||
public class Deny extends Behavior {
|
||||
public Deny() {
|
||||
super();
|
||||
this.behavior = Operation.deny;
|
||||
}
|
||||
|
||||
String message;
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public Deny setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message;
|
||||
|
||||
public interface Message {
|
||||
String getType();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(alphabetic = false, typeKey = "type", typeName = "MessageBase")
|
||||
public class MessageBase implements Message{
|
||||
protected String type;
|
||||
|
||||
public String toString() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
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;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "result")
|
||||
public class SDKResultMessage extends MessageBase {
|
||||
private String subtype; // 'error_max_turns' | 'error_during_execution'
|
||||
private String uuid;
|
||||
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
|
||||
@JSONField(name = "is_error")
|
||||
private boolean isError = true;
|
||||
|
||||
@JSONField(name = "duration_ms")
|
||||
private Long durationMs;
|
||||
|
||||
@JSONField(name = "duration_api_ms")
|
||||
private Long durationApiMs;
|
||||
|
||||
@JSONField(name = "num_turns")
|
||||
private Integer numTurns;
|
||||
private ExtendedUsage usage;
|
||||
private Map<String, Usage> modelUsage;
|
||||
|
||||
@JSONField(name = "permission_denials")
|
||||
private List<CLIPermissionDenial> permissionDenials;
|
||||
private Error error;
|
||||
|
||||
public SDKResultMessage() {
|
||||
super();
|
||||
this.type = "result";
|
||||
}
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return isError;
|
||||
}
|
||||
|
||||
public void setError(boolean error) {
|
||||
isError = error;
|
||||
}
|
||||
|
||||
public Long getDurationMs() {
|
||||
return durationMs;
|
||||
}
|
||||
|
||||
public void setDurationMs(Long durationMs) {
|
||||
this.durationMs = durationMs;
|
||||
}
|
||||
|
||||
public Long getDurationApiMs() {
|
||||
return durationApiMs;
|
||||
}
|
||||
|
||||
public void setDurationApiMs(Long durationApiMs) {
|
||||
this.durationApiMs = durationApiMs;
|
||||
}
|
||||
|
||||
public Integer getNumTurns() {
|
||||
return numTurns;
|
||||
}
|
||||
|
||||
public void setNumTurns(Integer numTurns) {
|
||||
this.numTurns = numTurns;
|
||||
}
|
||||
|
||||
public ExtendedUsage getUsage() {
|
||||
return usage;
|
||||
}
|
||||
|
||||
public void setUsage(ExtendedUsage usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
public Map<String, Usage> getModelUsage() {
|
||||
return modelUsage;
|
||||
}
|
||||
|
||||
public void setModelUsage(Map<String, Usage> modelUsage) {
|
||||
this.modelUsage = modelUsage;
|
||||
}
|
||||
|
||||
public List<CLIPermissionDenial> getPermissionDenials() {
|
||||
return permissionDenials;
|
||||
}
|
||||
|
||||
public void setPermissionDenials(List<CLIPermissionDenial> permissionDenials) {
|
||||
this.permissionDenials = permissionDenials;
|
||||
}
|
||||
|
||||
public Error getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(Error error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public static class Error {
|
||||
private String type;
|
||||
private String message;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
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;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "system")
|
||||
public class SDKSystemMessage extends MessageBase {
|
||||
private String subtype;
|
||||
private String uuid;
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
private Object data;
|
||||
private String cwd;
|
||||
private List<String> tools;
|
||||
@JSONField(name = "mcp_servers")
|
||||
private List<McpServer> mcpServers;
|
||||
private String model;
|
||||
@JSONField(name = "permission_mode")
|
||||
private String permissionMode;
|
||||
@JSONField(name = "slash_commands")
|
||||
private List<String> slashCommands;
|
||||
@JSONField(name = "qwen_code_version")
|
||||
private String qwenCodeVersion;
|
||||
@JSONField(name = "output_style")
|
||||
private String outputStyle;
|
||||
private List<String> agents;
|
||||
private List<String> skills;
|
||||
private Map<String, Object> capabilities;
|
||||
@JSONField(name = "compact_metadata")
|
||||
private CompactMetadata compactMetadata;
|
||||
|
||||
public SDKSystemMessage() {
|
||||
super();
|
||||
this.type = "system";
|
||||
}
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getCwd() {
|
||||
return cwd;
|
||||
}
|
||||
|
||||
public void setCwd(String cwd) {
|
||||
this.cwd = cwd;
|
||||
}
|
||||
|
||||
public List<String> getTools() {
|
||||
return tools;
|
||||
}
|
||||
|
||||
public void setTools(List<String> tools) {
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
public List<McpServer> getMcpServers() {
|
||||
return mcpServers;
|
||||
}
|
||||
|
||||
public void setMcpServers(List<McpServer> mcpServers) {
|
||||
this.mcpServers = mcpServers;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String getPermissionMode() {
|
||||
return permissionMode;
|
||||
}
|
||||
|
||||
public void setPermissionMode(String permissionMode) {
|
||||
this.permissionMode = permissionMode;
|
||||
}
|
||||
|
||||
public List<String> getSlashCommands() {
|
||||
return slashCommands;
|
||||
}
|
||||
|
||||
public void setSlashCommands(List<String> slashCommands) {
|
||||
this.slashCommands = slashCommands;
|
||||
}
|
||||
|
||||
public String getQwenCodeVersion() {
|
||||
return qwenCodeVersion;
|
||||
}
|
||||
|
||||
public void setQwenCodeVersion(String qwenCodeVersion) {
|
||||
this.qwenCodeVersion = qwenCodeVersion;
|
||||
}
|
||||
|
||||
public String getOutputStyle() {
|
||||
return outputStyle;
|
||||
}
|
||||
|
||||
public void setOutputStyle(String outputStyle) {
|
||||
this.outputStyle = outputStyle;
|
||||
}
|
||||
|
||||
public List<String> getAgents() {
|
||||
return agents;
|
||||
}
|
||||
|
||||
public void setAgents(List<String> agents) {
|
||||
this.agents = agents;
|
||||
}
|
||||
|
||||
public List<String> getSkills() {
|
||||
return skills;
|
||||
}
|
||||
|
||||
public void setSkills(List<String> skills) {
|
||||
this.skills = skills;
|
||||
}
|
||||
|
||||
public Map<String, Object> getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
public void setCapabilities(Map<String, Object> capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
public CompactMetadata getCompactMetadata() {
|
||||
return compactMetadata;
|
||||
}
|
||||
|
||||
public void setCompactMetadata(CompactMetadata compactMetadata) {
|
||||
this.compactMetadata = compactMetadata;
|
||||
}
|
||||
|
||||
public static class McpServer {
|
||||
private String name;
|
||||
private String status;
|
||||
|
||||
// Getters and setters
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CompactMetadata {
|
||||
private String trigger;
|
||||
|
||||
@JSONField(name = "pre_tokens")
|
||||
private Integer preTokens;
|
||||
|
||||
// Getters and setters
|
||||
public String getTrigger() {
|
||||
return trigger;
|
||||
}
|
||||
|
||||
public void setTrigger(String trigger) {
|
||||
this.trigger = trigger;
|
||||
}
|
||||
|
||||
public Integer getPreTokens() {
|
||||
return preTokens;
|
||||
}
|
||||
|
||||
public void setPreTokens(Integer preTokens) {
|
||||
this.preTokens = preTokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "user")
|
||||
public class SDKUserMessage extends MessageBase {
|
||||
private String uuid;
|
||||
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
private final APIUserMessage message = new APIUserMessage();
|
||||
|
||||
@JSONField(name = "parent_tool_use_id")
|
||||
private String parentToolUseId;
|
||||
private Map<String, String> options;
|
||||
|
||||
public SDKUserMessage() {
|
||||
super();
|
||||
this.setType("user");
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public SDKUserMessage setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDKUserMessage setContent(String content) {
|
||||
message.setContent(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return message.getContent();
|
||||
}
|
||||
|
||||
public String getParentToolUseId() {
|
||||
return parentToolUseId;
|
||||
}
|
||||
|
||||
public SDKUserMessage setParentToolUseId(String parentToolUseId) {
|
||||
this.parentToolUseId = parentToolUseId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public SDKUserMessage setOptions(Map<String, String> options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class APIUserMessage {
|
||||
private String role = "user";
|
||||
private String content;
|
||||
|
||||
// Getters and Setters
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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;
|
||||
|
||||
public class APIAssistantMessage {
|
||||
private String id;
|
||||
private String type = "message";
|
||||
private String role = "assistant";
|
||||
private String model;
|
||||
private List<ContentBlock> content;
|
||||
|
||||
@JSONField(name = "stop_reason")
|
||||
private String stopReason;
|
||||
private Usage usage;
|
||||
|
||||
// Getters and setters
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String getStopReason() {
|
||||
return stopReason;
|
||||
}
|
||||
|
||||
public void setStopReason(String stopReason) {
|
||||
this.stopReason = stopReason;
|
||||
}
|
||||
|
||||
public Usage getUsage() {
|
||||
return usage;
|
||||
}
|
||||
|
||||
public void setUsage(Usage usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
public List<ContentBlock> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(List<ContentBlock> content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "assistant")
|
||||
public class SDKAssistantMessage extends MessageBase {
|
||||
private String uuid;
|
||||
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
private APIAssistantMessage message;
|
||||
|
||||
@JSONField(name = "parent_tool_use_id")
|
||||
private String parentToolUseId;
|
||||
|
||||
public SDKAssistantMessage() {
|
||||
super();
|
||||
this.type = "assistant";
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public APIAssistantMessage getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(APIAssistantMessage message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getParentToolUseId() {
|
||||
return parentToolUseId;
|
||||
}
|
||||
|
||||
public void setParentToolUseId(String parentToolUseId) {
|
||||
this.parentToolUseId = parentToolUseId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "stream_event")
|
||||
public class SDKPartialAssistantMessage extends MessageBase {
|
||||
private String uuid;
|
||||
|
||||
@JSONField(name = "session_id")
|
||||
private String sessionId;
|
||||
private StreamEvent event;
|
||||
|
||||
@JSONField(name = "parent_tool_use_id")
|
||||
private String parentToolUseId;
|
||||
|
||||
public SDKPartialAssistantMessage() {
|
||||
super();
|
||||
this.type = "stream_event";
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public StreamEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public void setEvent(StreamEvent event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public String getParentToolUseId() {
|
||||
return parentToolUseId;
|
||||
}
|
||||
|
||||
public void setParentToolUseId(String parentToolUseId) {
|
||||
this.parentToolUseId = parentToolUseId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
public class Annotation {
|
||||
@JSONField(name = "type")
|
||||
private String type;
|
||||
|
||||
@JSONField(name = "value")
|
||||
private String value;
|
||||
|
||||
// Getters and setters
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "ContentBlock", seeAlso = { TextBlock.class, ToolResultBlock.class, ThinkingBlock.class, ToolUseBlock.class })
|
||||
public abstract class ContentBlock implements AssistantContent {
|
||||
protected String type;
|
||||
protected List<Annotation> annotations;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<Annotation> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void setAnnotations(List<Annotation> annotations) {
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "text")
|
||||
public class TextBlock extends ContentBlock {
|
||||
private String text;
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "thinking")
|
||||
public class ThinkingBlock extends ContentBlock{
|
||||
private String thinking;
|
||||
private String signature;
|
||||
|
||||
public String getThinking() {
|
||||
return thinking;
|
||||
}
|
||||
|
||||
public void setThinking(String thinking) {
|
||||
this.thinking = thinking;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() {
|
||||
return thinking;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "tool_result")
|
||||
public class ToolResultBlock extends ContentBlock {
|
||||
@JSONField(name = "tool_use_id")
|
||||
private String toolUseId;
|
||||
|
||||
@JSONField(name = "content")
|
||||
private Object content; // Can be String or List<ContentBlock>
|
||||
|
||||
@JSONField(name = "is_error")
|
||||
private Boolean isError;
|
||||
|
||||
public String getToolUseId() {
|
||||
return toolUseId;
|
||||
}
|
||||
|
||||
public void setToolUseId(String toolUseId) {
|
||||
this.toolUseId = toolUseId;
|
||||
}
|
||||
|
||||
public Object getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(Object content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Boolean getIsError() {
|
||||
return isError;
|
||||
}
|
||||
|
||||
public void setIsError(Boolean isError) {
|
||||
this.isError = isError;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.block;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "tool_use")
|
||||
public class ToolUseBlock extends ContentBlock {
|
||||
private String id;
|
||||
private String name;
|
||||
private Map<String, Object> input;
|
||||
private List<Annotation> annotations;
|
||||
|
||||
// 构造函数
|
||||
public ToolUseBlock() {}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Map<String, Object> getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
public void setInput(Map<String, Object> input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
public List<Annotation> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void setAnnotations(List<Annotation> annotations) {
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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.data.AssistantContent;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "content_block_delta")
|
||||
public class ContentBlockDeltaEvent extends StreamEvent {
|
||||
private int index;
|
||||
private ContentBlockDelta delta;
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public ContentBlockDelta getDelta() {
|
||||
return delta;
|
||||
}
|
||||
|
||||
public void setDelta(ContentBlockDelta delta) {
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "ContentBlockDelta",
|
||||
seeAlso = {ContentBlockDeltaText.class, ContentBlockDeltaThinking.class, ContentBlockDeltaInputJson.class})
|
||||
public abstract static class ContentBlockDelta implements AssistantContent {
|
||||
private String type;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "text_delta")
|
||||
public static class ContentBlockDeltaText extends ContentBlockDelta {
|
||||
private String text;
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "thinking_delta")
|
||||
public static class ContentBlockDeltaThinking extends ContentBlockDelta {
|
||||
private String thinking;
|
||||
|
||||
public String getThinking() {
|
||||
return thinking;
|
||||
}
|
||||
|
||||
public void setThinking(String thinking) {
|
||||
this.thinking = thinking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() {
|
||||
return thinking;
|
||||
}
|
||||
}
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "input_json_delta")
|
||||
public static class ContentBlockDeltaInputJson extends ContentBlockDelta {
|
||||
@JSONField(name = "partial_json")
|
||||
private String partialJson;
|
||||
|
||||
public String getPartialJson() {
|
||||
return partialJson;
|
||||
}
|
||||
|
||||
public void setPartialJson(String partialJson) {
|
||||
this.partialJson = partialJson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() {
|
||||
return partialJson;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "content_block_start")
|
||||
public class ContentBlockStartEvent extends StreamEvent{
|
||||
private int index;
|
||||
|
||||
@JSONField(name = "content_block")
|
||||
private ContentBlock contentBlock;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "content_block_stop")
|
||||
public class ContentBlockStopEvent extends StreamEvent{
|
||||
Long index;
|
||||
|
||||
public Long getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(Long index) {
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeName = "message_start")
|
||||
public class MessageStartStreamEvent extends StreamEvent{
|
||||
private Message message;
|
||||
|
||||
public static class Message {
|
||||
private String id;
|
||||
private String role;
|
||||
private String model;
|
||||
|
||||
// Getters and setters
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
public Message getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(Message message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeName = "message_stop")
|
||||
public class MessageStopStreamEvent extends StreamEvent{
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.assistant.event;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONType;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "StreamEvent",
|
||||
seeAlso = {MessageStartStreamEvent.class, MessageStopStreamEvent.class, ContentBlockStartEvent.class, ContentBlockStopEvent.class,
|
||||
ContentBlockDeltaEvent.class})
|
||||
public class StreamEvent {
|
||||
protected String type;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.InitializeConfig;
|
||||
|
||||
public class CLIControlInitializeRequest {
|
||||
String subtype = "initialize";
|
||||
|
||||
@JSONField(unwrapped = true)
|
||||
InitializeConfig initializeConfig = new InitializeConfig();
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
public InitializeConfig getInitializeConfig() {
|
||||
return initializeConfig;
|
||||
}
|
||||
|
||||
public CLIControlInitializeRequest setInitializeConfig(InitializeConfig initializeConfig) {
|
||||
this.initializeConfig = initializeConfig;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
import com.alibaba.qwen.code.cli.protocol.data.Capabilities;
|
||||
|
||||
public class CLIControlInitializeResponse {
|
||||
String subtype = "initialize";
|
||||
Capabilities capabilities;
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
public Capabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
public void setCapabilities(Capabilities capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
public class CLIControlInterruptRequest {
|
||||
String subtype = "interrupt";
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
public class CLIControlPermissionRequest {
|
||||
private String subtype;
|
||||
|
||||
@JSONField(name = "tool_name")
|
||||
private String toolName;
|
||||
|
||||
@JSONField(name = "tool_use_id")
|
||||
private String toolUseId;
|
||||
|
||||
private Map<String, Object> input;
|
||||
|
||||
@JSONField(name = "permission_suggestions")
|
||||
private List<PermissionSuggestion> permissionSuggestions;
|
||||
|
||||
@JSONField(name = "blocked_path")
|
||||
private String blockedPath;
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
public String getToolName() {
|
||||
return toolName;
|
||||
}
|
||||
|
||||
public void setToolName(String toolName) {
|
||||
this.toolName = toolName;
|
||||
}
|
||||
|
||||
public String getToolUseId() {
|
||||
return toolUseId;
|
||||
}
|
||||
|
||||
public void setToolUseId(String toolUseId) {
|
||||
this.toolUseId = toolUseId;
|
||||
}
|
||||
|
||||
public Map<String, Object> getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
public void setInput(Map<String, Object> input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
public List<PermissionSuggestion> getPermissionSuggestions() {
|
||||
return permissionSuggestions;
|
||||
}
|
||||
|
||||
public void setPermissionSuggestions(
|
||||
List<PermissionSuggestion> permissionSuggestions) {
|
||||
this.permissionSuggestions = permissionSuggestions;
|
||||
}
|
||||
|
||||
public String getBlockedPath() {
|
||||
return blockedPath;
|
||||
}
|
||||
|
||||
public void setBlockedPath(String blockedPath) {
|
||||
this.blockedPath = blockedPath;
|
||||
}
|
||||
|
||||
public static class PermissionSuggestion {
|
||||
private String type; // 'allow' | 'deny' | 'modify'
|
||||
private String label;
|
||||
private String description;
|
||||
private Object modifiedInput;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Object getModifiedInput() {
|
||||
return modifiedInput;
|
||||
}
|
||||
|
||||
public void setModifiedInput(Object modifiedInput) {
|
||||
this.modifiedInput = modifiedInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior;
|
||||
|
||||
public class CLIControlPermissionResponse {
|
||||
private String subtype = "can_use_tool";
|
||||
|
||||
@JSONField(unwrapped = true)
|
||||
Behavior behavior;
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
public Behavior getBehavior() {
|
||||
return behavior;
|
||||
}
|
||||
|
||||
public CLIControlPermissionResponse setBehavior(Behavior behavior) {
|
||||
this.behavior = behavior;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "control_request")
|
||||
public class CLIControlRequest<R> extends MessageBase {
|
||||
@JSONField(name = "request_id")
|
||||
private String requestId = UUID.randomUUID().toString();
|
||||
|
||||
private R request;
|
||||
|
||||
public CLIControlRequest() {
|
||||
super();
|
||||
type = "control_request";
|
||||
}
|
||||
|
||||
public static <T> CLIControlRequest<T> create(T request) {
|
||||
CLIControlRequest<T> controlRequest = new CLIControlRequest<>();
|
||||
controlRequest.setRequest(request);
|
||||
return controlRequest;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public CLIControlRequest<R> setRequestId(String requestId) {
|
||||
this.requestId = requestId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public R getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public CLIControlRequest<R> setRequest(R request) {
|
||||
this.request = request;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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;
|
||||
|
||||
@JSONType(typeKey = "type", typeName = "control_response")
|
||||
public class CLIControlResponse<R> extends MessageBase {
|
||||
private Response<R> response;
|
||||
|
||||
public CLIControlResponse() {
|
||||
super();
|
||||
this.type = "control_response";
|
||||
}
|
||||
|
||||
public Response<R> getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public void setResponse(Response<R> response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public Response<R> createResponse() {
|
||||
Response<R> response = new Response<>();
|
||||
this.setResponse(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static class Response<R> {
|
||||
@JSONField(name = "request_id")
|
||||
private String requestId;
|
||||
private String subtype = "success";
|
||||
R response;
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public Response<R> setRequestId(String requestId) {
|
||||
this.requestId = requestId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public Response<R> setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
return this;
|
||||
}
|
||||
|
||||
public R getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public Response<R> setResponse(R response) {
|
||||
this.response = response;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
public class CLIControlSetModelRequest {
|
||||
String subtype = "set_model";
|
||||
String model;
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
public class CLIControlSetModelResponse {
|
||||
String subtype = "set_model";
|
||||
String model;
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.alibaba.qwen.code.cli.protocol.message.control;
|
||||
|
||||
public class CLIControlSetPermissionModeRequest {
|
||||
String subtype = "set_permission_mode";
|
||||
|
||||
String mode;
|
||||
|
||||
public String getSubtype() {
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public void setSubtype(String subtype) {
|
||||
this.subtype = subtype;
|
||||
}
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
}
|
||||
@@ -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,254 @@
|
||||
package com.alibaba.qwen.code.cli.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
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.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.control.CLIControlInitializeRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlInitializeResponse;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlInterruptRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlPermissionRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.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.CLIControlSetModelRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlSetPermissionModeRequest;
|
||||
import com.alibaba.qwen.code.cli.session.event.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.utils.MyConcurrentUtils;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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;
|
||||
|
||||
public Session(Transport transport) throws SessionControlException {
|
||||
if (transport == null || !transport.isAvailable()) {
|
||||
throw new SessionControlException("Transport is not available");
|
||||
}
|
||||
this.transport = transport;
|
||||
start();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws SessionControlException {
|
||||
try {
|
||||
transport.close();
|
||||
} catch (Exception e) {
|
||||
throw new SessionControlException("Failed to close the session", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Boolean> interrupt() throws SessionControlException {
|
||||
checkAvailable();
|
||||
return processControlRequest(new CLIControlRequest<CLIControlInterruptRequest>().setRequest(new CLIControlInterruptRequest()).toString());
|
||||
}
|
||||
|
||||
public Optional<Boolean> setModel(String modelName) throws SessionControlException {
|
||||
checkAvailable();
|
||||
CLIControlSetModelRequest cliControlSetModelRequest = new CLIControlSetModelRequest();
|
||||
cliControlSetModelRequest.setModel(modelName);
|
||||
return processControlRequest(new CLIControlRequest<CLIControlSetModelRequest>().setRequest(cliControlSetModelRequest).toString());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void continueSession() throws SessionControlException {
|
||||
resumeSession(getSessionId());
|
||||
}
|
||||
|
||||
public void resumeSession(String sessionId) throws SessionControlException {
|
||||
if (StringUtils.isNotBlank(sessionId)) {
|
||||
transport.getTransportOptions().setResumeSessionId(sessionId);
|
||||
}
|
||||
this.start();
|
||||
}
|
||||
|
||||
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)).orElse(defaultEventTimeout));
|
||||
return false;
|
||||
} else if ("assistant".equals(messageType)) {
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onAssistantMessage(this, jsonObject.to(SDKAssistantMessage.class)),
|
||||
Optional.ofNullable(sessionEventConsumers.onAssistantMessageTimeout(this)).orElse(defaultEventTimeout));
|
||||
return false;
|
||||
} else if ("stream_event".equals(messageType)) {
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onPartialAssistantMessage(this, jsonObject.to(SDKPartialAssistantMessage.class)),
|
||||
Optional.ofNullable(sessionEventConsumers.onPartialAssistantMessageTimeout(this)).orElse(defaultEventTimeout));
|
||||
return false;
|
||||
} else if ("user".equals(messageType)) {
|
||||
MyConcurrentUtils.runAndWait(
|
||||
() -> sessionEventConsumers.onUserMessage(this, jsonObject.to(SDKUserMessage.class, Feature.FieldBased)),
|
||||
Optional.ofNullable(sessionEventConsumers.onUserMessageTimeout(this)).orElse(defaultEventTimeout));
|
||||
return false;
|
||||
} else if ("result".equals(messageType)) {
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onResultMessage(this, jsonObject.to(SDKResultMessage.class)),
|
||||
Optional.ofNullable(sessionEventConsumers.onResultMessageTimeout(this)).orElse(defaultEventTimeout));
|
||||
return true;
|
||||
} else if ("control_response".equals(messageType)) {
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onControlResponse(this, jsonObject.to(CLIControlResponse.class)),
|
||||
Optional.ofNullable(sessionEventConsumers.onControlResponseTimeout(this)).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)) {
|
||||
return processControlRequestInThePrompting(jsonObject, sessionEventConsumers);
|
||||
} else {
|
||||
log.warn("unknown message type: {}", messageType);
|
||||
MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onOtherMessage(this, line),
|
||||
Optional.ofNullable(sessionEventConsumers.onOtherMessageTimeout(this)).orElse(defaultEventTimeout));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new SessionSendPromptException("Failed to send prompt", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processControlRequestInThePrompting(JSONObject jsonObject, SessionEventConsumers sessionEventConsumers) {
|
||||
String subType = Optional.of(jsonObject)
|
||||
.map(cr -> cr.getJSONObject("request"))
|
||||
.map(r -> r.getString("subtype"))
|
||||
.orElse("");
|
||||
if ("can_use_tool".equals(subType)) {
|
||||
try {
|
||||
return processPermissionResponse(jsonObject, sessionEventConsumers);
|
||||
} catch (IOException | ExecutionException | InterruptedException | TimeoutException e) {
|
||||
log.error("Failed to process permission response", e);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
CLIControlResponse<?> cliControlResponse;
|
||||
try {
|
||||
cliControlResponse = MyConcurrentUtils.runAndWait(
|
||||
() -> sessionEventConsumers.onControlRequest(this, jsonObject.to(new TypeReference<CLIControlRequest<?>>() {})),
|
||||
Optional.ofNullable(sessionEventConsumers.onControlRequestTimeout(this)).orElse(defaultEventTimeout));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to process control request", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cliControlResponse != null) {
|
||||
try {
|
||||
transport.inputNoWaitResponse(cliControlResponse.toString());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to process control response", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processPermissionResponse(JSONObject jsonObject, SessionEventConsumers sessionEventConsumers)
|
||||
throws IOException, ExecutionException, InterruptedException, TimeoutException {
|
||||
CLIControlRequest<CLIControlPermissionRequest> permissionRequest = jsonObject.to(
|
||||
new TypeReference<CLIControlRequest<CLIControlPermissionRequest>>() {});
|
||||
|
||||
Behavior behavior = Optional.ofNullable(MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onPermissionRequest(this, permissionRequest),
|
||||
Optional.ofNullable(sessionEventConsumers.onPermissionRequestTimeout(this)).orElse(defaultEventTimeout)))
|
||||
.map(b -> {
|
||||
if (b instanceof Allow) {
|
||||
Allow allow = (Allow) b;
|
||||
if (allow.getUpdatedInput() == null) {
|
||||
allow.setUpdatedInput(permissionRequest.getRequest().getInput());
|
||||
}
|
||||
}
|
||||
return b;
|
||||
})
|
||||
.orElse(Behavior.defaultBehavior());
|
||||
CLIControlResponse<CLIControlPermissionResponse> permissionResponse = new CLIControlResponse<>();
|
||||
permissionResponse.createResponse().setResponse(new CLIControlPermissionResponse().setBehavior(behavior)).setRequestId(
|
||||
permissionRequest.getRequestId());
|
||||
String permissionMessage = permissionResponse.toString();
|
||||
log.debug("send permission message to agent: {}", permissionMessage);
|
||||
transport.inputNoWaitResponse(permissionMessage);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return Optional.ofNullable(lastSdkSystemMessage).map(SDKSystemMessage::getSessionId).orElse(null);
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return transport.isAvailable();
|
||||
}
|
||||
|
||||
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,51 @@
|
||||
package com.alibaba.qwen.code.cli.session.event;
|
||||
|
||||
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.control.CLIControlPermissionRequest;
|
||||
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.session.Session;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
public interface SessionEventConsumers {
|
||||
void onSystemMessage(Session session, SDKSystemMessage systemMessage);
|
||||
|
||||
void onResultMessage(Session session, SDKResultMessage resultMessage);
|
||||
|
||||
void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage);
|
||||
|
||||
void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage);
|
||||
|
||||
void onUserMessage(Session session, SDKUserMessage userMessage);
|
||||
|
||||
void onOtherMessage(Session session, String message);
|
||||
|
||||
void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse);
|
||||
|
||||
CLIControlResponse<?> onControlRequest(Session session, CLIControlRequest<?> cliControlRequest);
|
||||
|
||||
Behavior onPermissionRequest(Session session, CLIControlRequest<CLIControlPermissionRequest> permissionRequest);
|
||||
|
||||
Timeout onSystemMessageTimeout(Session session);
|
||||
|
||||
Timeout onResultMessageTimeout(Session session);
|
||||
|
||||
Timeout onAssistantMessageTimeout(Session session);
|
||||
|
||||
Timeout onPartialAssistantMessageTimeout(Session session);
|
||||
|
||||
Timeout onUserMessageTimeout(Session session);
|
||||
|
||||
Timeout onOtherMessageTimeout(Session session);
|
||||
|
||||
Timeout onControlResponseTimeout(Session session);
|
||||
|
||||
Timeout onControlRequestTimeout(Session session);
|
||||
|
||||
Timeout onPermissionRequestTimeout(Session session);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.alibaba.qwen.code.cli.session.event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.alibaba.qwen.code.cli.protocol.data.AssistantContent;
|
||||
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.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.event.ContentBlockDeltaEvent;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.assistant.event.StreamEvent;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlPermissionRequest;
|
||||
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.session.Session;
|
||||
import com.alibaba.qwen.code.cli.utils.Timeout;
|
||||
|
||||
public class SessionEventSimpleConsumers implements SessionEventConsumers {
|
||||
@Override
|
||||
public void onSystemMessage(Session session, SDKSystemMessage systemMessage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResultMessage(Session session, SDKResultMessage resultMessage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) {
|
||||
onAssistantMessageIncludePartial(session, Optional.ofNullable(assistantMessage.getMessage().getContent())
|
||||
.map(cbs -> cbs.stream().map(cb -> (AssistantContent) cb).collect(Collectors.toList()))
|
||||
.orElse(new ArrayList<>()), AssistantMessageOutputType.entire);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) {
|
||||
StreamEvent event = partialAssistantMessage.getEvent();
|
||||
if (!(event instanceof ContentBlockDeltaEvent)) {
|
||||
return;
|
||||
}
|
||||
onAssistantMessageIncludePartial(session, Collections.singletonList(((ContentBlockDeltaEvent) event).getDelta()), AssistantMessageOutputType.partial);
|
||||
}
|
||||
|
||||
public void onAssistantMessageIncludePartial(Session session, List<AssistantContent> assistantContents,
|
||||
AssistantMessageOutputType assistantMessageOutputType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserMessage(Session session, SDKUserMessage userMessage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOtherMessage(Session session, String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onControlResponse(Session session, CLIControlResponse<?> cliControlResponse) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CLIControlResponse<?> onControlRequest(Session session, CLIControlRequest<?> cliControlRequest) {
|
||||
return new CLIControlResponse<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Behavior onPermissionRequest(Session session, CLIControlRequest<CLIControlPermissionRequest> permissionRequest) {
|
||||
if (Operation.deny.equals(this.defaultPermissionOperation)) {
|
||||
return new Deny().setMessage("Permission denied.");
|
||||
} else {
|
||||
return new Allow().setUpdatedInput(permissionRequest.getRequest().getInput());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onSystemMessageTimeout(Session session) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onResultMessageTimeout(Session session) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onAssistantMessageTimeout(Session session) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onPartialAssistantMessageTimeout(Session session) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onUserMessageTimeout(Session session) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onOtherMessageTimeout(Session session) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onControlResponseTimeout(Session session) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onControlRequestTimeout(Session session) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onPermissionRequestTimeout(Session session) {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
public Timeout getDefaultEventTimeout() {
|
||||
return defaultEventTimeout;
|
||||
}
|
||||
|
||||
public SessionEventSimpleConsumers setDefaultEventTimeout(Timeout defaultEventTimeout) {
|
||||
this.defaultEventTimeout = defaultEventTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operation getDefaultPermissionOperation() {
|
||||
return defaultPermissionOperation;
|
||||
}
|
||||
|
||||
public SessionEventSimpleConsumers setDefaultPermissionOperation(Operation defaultPermissionOperation) {
|
||||
this.defaultPermissionOperation = defaultPermissionOperation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SessionEventSimpleConsumers() {
|
||||
}
|
||||
|
||||
public SessionEventSimpleConsumers(Operation defaultPermissionOperation, Timeout defaultEventTimeout) {
|
||||
this.defaultPermissionOperation = defaultPermissionOperation;
|
||||
this.defaultEventTimeout = defaultEventTimeout;
|
||||
}
|
||||
|
||||
private Operation defaultPermissionOperation = Operation.deny;
|
||||
protected Timeout defaultEventTimeout = Timeout.TIMEOUT_60_SECONDS;
|
||||
|
||||
public enum AssistantMessageOutputType {
|
||||
entire,
|
||||
partial
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.alibaba.qwen.code.cli.session.exception;
|
||||
|
||||
public class SessionControlException extends Exception {
|
||||
public SessionControlException() {
|
||||
}
|
||||
|
||||
public SessionControlException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SessionControlException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public SessionControlException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public SessionControlException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.alibaba.qwen.code.cli.session.exception;
|
||||
|
||||
public class SessionSendPromptException extends Exception {
|
||||
public SessionSendPromptException() {
|
||||
}
|
||||
|
||||
public SessionSendPromptException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SessionSendPromptException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public SessionSendPromptException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public SessionSendPromptException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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;
|
||||
|
||||
public interface Transport {
|
||||
TransportOptions getTransportOptions();
|
||||
|
||||
boolean isReading();
|
||||
|
||||
void start() throws IOException;
|
||||
|
||||
void close() throws IOException;
|
||||
|
||||
boolean isAvailable();
|
||||
|
||||
String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException;
|
||||
|
||||
void inputWaitForMultiLine(String message, Function<String, Boolean> callBackFunction) throws IOException;
|
||||
|
||||
void inputNoWaitResponse(String message) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
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;
|
||||
|
||||
public class TransportOptions implements Cloneable {
|
||||
private String pathToQwenExecutable;
|
||||
private String cwd;
|
||||
private String model;
|
||||
private PermissionMode permissionMode;
|
||||
private Map<String, String> env;
|
||||
private Integer maxSessionTurns;
|
||||
private List<String> coreTools;
|
||||
private List<String> excludeTools;
|
||||
private List<String> allowedTools;
|
||||
private String authType;
|
||||
private Boolean includePartialMessages;
|
||||
private Boolean skillsEnable;
|
||||
private Timeout turnTimeout;
|
||||
private Timeout messageTimeout;
|
||||
private String resumeSessionId;
|
||||
private List<String> otherOptions;
|
||||
|
||||
public String getPathToQwenExecutable() {
|
||||
return pathToQwenExecutable;
|
||||
}
|
||||
|
||||
public TransportOptions setPathToQwenExecutable(String pathToQwenExecutable) {
|
||||
this.pathToQwenExecutable = pathToQwenExecutable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCwd() {
|
||||
return cwd;
|
||||
}
|
||||
|
||||
public TransportOptions setCwd(String cwd) {
|
||||
this.cwd = cwd;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public TransportOptions setModel(String model) {
|
||||
this.model = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PermissionMode getPermissionMode() {
|
||||
return permissionMode;
|
||||
}
|
||||
|
||||
public TransportOptions setPermissionMode(PermissionMode permissionMode) {
|
||||
this.permissionMode = permissionMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getEnv() {
|
||||
return env;
|
||||
}
|
||||
|
||||
public TransportOptions setEnv(Map<String, String> env) {
|
||||
this.env = env;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getMaxSessionTurns() {
|
||||
return maxSessionTurns;
|
||||
}
|
||||
|
||||
public TransportOptions setMaxSessionTurns(Integer maxSessionTurns) {
|
||||
this.maxSessionTurns = maxSessionTurns;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getCoreTools() {
|
||||
return coreTools;
|
||||
}
|
||||
|
||||
public TransportOptions setCoreTools(List<String> coreTools) {
|
||||
this.coreTools = coreTools;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getExcludeTools() {
|
||||
return excludeTools;
|
||||
}
|
||||
|
||||
public TransportOptions setExcludeTools(List<String> excludeTools) {
|
||||
this.excludeTools = excludeTools;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getAllowedTools() {
|
||||
return allowedTools;
|
||||
}
|
||||
|
||||
public TransportOptions setAllowedTools(List<String> allowedTools) {
|
||||
this.allowedTools = allowedTools;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getAuthType() {
|
||||
return authType;
|
||||
}
|
||||
|
||||
public TransportOptions setAuthType(String authType) {
|
||||
this.authType = authType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Boolean getIncludePartialMessages() {
|
||||
return includePartialMessages;
|
||||
}
|
||||
|
||||
public TransportOptions setIncludePartialMessages(Boolean includePartialMessages) {
|
||||
this.includePartialMessages = includePartialMessages;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Boolean getSkillsEnable() {
|
||||
return skillsEnable;
|
||||
}
|
||||
|
||||
public TransportOptions setSkillsEnable(Boolean skillsEnable) {
|
||||
this.skillsEnable = skillsEnable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Timeout getTurnTimeout() {
|
||||
return turnTimeout;
|
||||
}
|
||||
|
||||
public TransportOptions setTurnTimeout(Timeout turnTimeout) {
|
||||
this.turnTimeout = turnTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Timeout getMessageTimeout() {
|
||||
return messageTimeout;
|
||||
}
|
||||
|
||||
public TransportOptions setMessageTimeout(Timeout messageTimeout) {
|
||||
this.messageTimeout = messageTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getResumeSessionId() {
|
||||
return resumeSessionId;
|
||||
}
|
||||
|
||||
public TransportOptions setResumeSessionId(String resumeSessionId) {
|
||||
this.resumeSessionId = resumeSessionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getOtherOptions() {
|
||||
return otherOptions;
|
||||
}
|
||||
|
||||
public TransportOptions setOtherOptions(List<String> otherOptions) {
|
||||
this.otherOptions = otherOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOptions clone() {
|
||||
try {
|
||||
return (TransportOptions) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
public ProcessTransport() throws IOException {
|
||||
this(new TransportOptions());
|
||||
}
|
||||
|
||||
public ProcessTransport(TransportOptions transportOptions) throws IOException {
|
||||
this(transportOptions, (line) -> log.error("process error: {}", line));
|
||||
}
|
||||
|
||||
public ProcessTransport(TransportOptions transportOptions, Consumer<String> errorHandler) throws IOException {
|
||||
this.transportOptions = transportOptions;
|
||||
this.errorHandler = errorHandler;
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOptions getTransportOptions() {
|
||||
return transportOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReading() {
|
||||
return reading.get();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return process != null && process.isAlive();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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,119 @@
|
||||
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;
|
||||
|
||||
class TransportOptionsAdapter {
|
||||
TransportOptions transportOptions;
|
||||
private static final Timeout DEFAULT_TURN_TIMEOUT = new Timeout(1000 * 60 * 30L, TimeUnit.MILLISECONDS);
|
||||
private static final Timeout DEFAULT_MESSAGE_TIMEOUT = new Timeout(1000 * 60 * 3L, TimeUnit.MILLISECONDS);
|
||||
|
||||
TransportOptionsAdapter(TransportOptions userTransportOptions) {
|
||||
transportOptions = addDefaultTransportOptions(userTransportOptions);
|
||||
}
|
||||
|
||||
TransportOptions getHandledTransportOptions() {
|
||||
return transportOptions;
|
||||
}
|
||||
|
||||
String getCwd() {
|
||||
return transportOptions.getCwd();
|
||||
}
|
||||
|
||||
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[] {});
|
||||
}
|
||||
|
||||
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,65 @@
|
||||
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;
|
||||
|
||||
public class MyConcurrentUtils {
|
||||
private static final Logger log = LoggerFactory.getLogger(MyConcurrentUtils.class);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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,47 @@
|
||||
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;
|
||||
|
||||
public class ThreadPoolConfig {
|
||||
private static final ThreadPoolExecutor defaultExecutor = new ThreadPoolExecutor(
|
||||
10, 30, 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;
|
||||
public static void setExecutorSupplier(Supplier<ThreadPoolExecutor> executorSupplier) {
|
||||
ThreadPoolConfig.executorSupplier = executorSupplier;
|
||||
}
|
||||
|
||||
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,27 @@
|
||||
package com.alibaba.qwen.code.cli.utils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
public class Timeout {
|
||||
private final Long value;
|
||||
private final TimeUnit 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;
|
||||
}
|
||||
|
||||
public Long getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public TimeUnit getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public static final Timeout TIMEOUT_60_SECONDS = new Timeout(60L, TimeUnit.SECONDS);
|
||||
public static final Timeout TIMEOUT_30_MINUTES = new Timeout(60L, TimeUnit.MINUTES);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.alibaba.qwen.code.cli;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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("result: {}", result);
|
||||
assertNotNull(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package com.alibaba.qwen.code.cli.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
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.AssistantContent;
|
||||
import com.alibaba.qwen.code.cli.protocol.data.PermissionMode;
|
||||
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.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.CLIControlPermissionRequest;
|
||||
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.session.event.SessionEventConsumers;
|
||||
import com.alibaba.qwen.code.cli.session.event.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 IOException, 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() {
|
||||
@Override
|
||||
public void onAssistantMessageIncludePartial(Session session, List<AssistantContent> assistantContents,
|
||||
AssistantMessageOutputType assistantMessageOutputType) {
|
||||
log.info("onAssistantMessageIncludePartial: {}", JSON.toJSONString(assistantContents));
|
||||
}
|
||||
}.setDefaultPermissionOperation(Operation.allow));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setPermissionModeSuccessfully() throws IOException, 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() {
|
||||
public Behavior onPermissionRequest(Session session, CLIControlRequest<CLIControlPermissionRequest> permissionRequest) {
|
||||
log.info("permissionRequest: {}", permissionRequest);
|
||||
return new Allow().setUpdatedInput(permissionRequest.getRequest().getInput());
|
||||
}
|
||||
});
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendPromptAndSetModelSuccessfully() throws IOException, 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("查看下当前目录有多少个文件", 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("查看下当前目录有多少个xml文件", new SessionEventSimpleConsumers());
|
||||
writeSplitLine("prompt 3 end");
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendPromptAndInterruptContinueSuccessfully() throws IOException, 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeout onPermissionRequestTimeout(Session session) {
|
||||
return Timeout.TIMEOUT_30_MINUTES;
|
||||
}
|
||||
}.setDefaultEventTimeout(new Timeout(90L, TimeUnit.SECONDS));
|
||||
session.sendPrompt("查看下当前目录有多少个文件", 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.CLIControlInitializeRequest;
|
||||
import com.alibaba.qwen.code.cli.protocol.message.control.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": {
|
||||
|
||||
Reference in New Issue
Block a user