From d9928eab664a7280315c4c59867d525cf37b5eed Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Thu, 4 Dec 2025 15:20:45 +0800 Subject: [PATCH 01/65] fix: improve windows background process handling and cleanup --- .gitignore | 3 + .../src/services/shellExecutionService.ts | 55 +++++++++++++- packages/core/src/tools/shell.ts | 74 ++++++++++++++++++- 3 files changed, 125 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 2c3156b96..bf3aa4211 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ gha-creds-*.json # Log files patch_output.log + +# test files +demo-app \ No newline at end of file diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index be0c26ff7..a9ad273bf 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -7,7 +7,7 @@ import stripAnsi from 'strip-ansi'; import type { PtyImplementation } from '../utils/getPty.js'; import { getPty } from '../utils/getPty.js'; -import { spawn as cpSpawn } from 'node:child_process'; +import { spawn as cpSpawn, spawnSync } from 'node:child_process'; import { TextDecoder } from 'node:util'; import os from 'node:os'; import type { IPty } from '@lydell/node-pty'; @@ -106,6 +106,51 @@ const getFullBufferText = (terminal: pkg.Terminal): string => { export class ShellExecutionService { private static activePtys = new Map(); + private static activeChildProcesses = new Set(); + + static { + const cleanup = () => { + // Cleanup PTYs + for (const [pid, pty] of this.activePtys) { + try { + if (os.platform() === 'win32') { + pty.ptyProcess.kill(); + } else { + process.kill(-pid, 'SIGKILL'); + } + } catch { + // ignore + } + } + + // Cleanup child processes + for (const pid of this.activeChildProcesses) { + try { + if (os.platform() === 'win32') { + spawnSync('taskkill', ['/pid', pid.toString(), '/f', '/t']); + } else { + process.kill(-pid, 'SIGKILL'); + } + } catch { + // ignore + } + } + }; + + process.on('exit', cleanup); + + // Ensure cleanup happens on SIGINT/SIGTERM + const signalHandler = () => { + process.exit(); + }; + + // We only attach these if we are in a node environment where we can control the process + if (typeof process !== 'undefined' && process.on) { + process.on('SIGINT', signalHandler); + process.on('SIGTERM', signalHandler); + } + } + /** * Executes a shell command using `node-pty`, capturing all output and lifecycle events. * @@ -281,9 +326,13 @@ export class ShellExecutionService { abortSignal.addEventListener('abort', abortHandler, { once: true }); + if (child.pid) { + this.activeChildProcesses.add(child.pid); + } + child.on('exit', (code, signal) => { if (child.pid) { - this.activePtys.delete(child.pid); + this.activeChildProcesses.delete(child.pid); } handleExit(code, signal); }); @@ -310,7 +359,7 @@ export class ShellExecutionService { } }); - return { pid: undefined, result }; + return { pid: child.pid, result }; } catch (e) { const error = e as Error; return { diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index 17e40dbe7..b303b0fac 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -29,6 +29,7 @@ import { summarizeToolOutput } from '../utils/summarizer.js'; import type { ShellExecutionConfig, ShellOutputEvent, + ShellExecutionResult, } from '../services/shellExecutionService.js'; import { ShellExecutionService } from '../services/shellExecutionService.js'; import { formatMemoryUsage } from '../utils/formatters.js'; @@ -47,6 +48,7 @@ export interface ShellToolParams { is_background: boolean; description?: string; directory?: string; + timeout?: number; } export class ShellToolInvocation extends BaseToolInvocation< @@ -132,6 +134,14 @@ export class ShellToolInvocation extends BaseToolInvocation< .toString('hex')}.tmp`; const tempFilePath = path.join(os.tmpdir(), tempFileName); + const timeoutMs = this.params.timeout ?? 3600000; + const abortController = new AbortController(); + const onAbort = () => abortController.abort(); + signal.addEventListener('abort', onAbort); + const timeoutId = setTimeout(() => { + abortController.abort(); + }, timeoutMs); + try { // Add co-author to git commit commands const processedCommand = this.addCoAuthorToGitCommit(strippedCommand); @@ -139,11 +149,30 @@ export class ShellToolInvocation extends BaseToolInvocation< const shouldRunInBackground = this.params.is_background; let finalCommand = processedCommand; - // If explicitly marked as background and doesn't already end with &, add it - if (shouldRunInBackground && !finalCommand.trim().endsWith('&')) { + // On non-Windows, use & to run in background. + // On Windows, we don't use start /B because it creates a detached process that + // doesn't die when the parent dies. Instead, we rely on the race logic below + // to return early while keeping the process attached (detached: false). + if ( + !isWindows && + shouldRunInBackground && + !finalCommand.trim().endsWith('&') + ) { finalCommand = finalCommand.trim() + ' &'; } + // On Windows, append a keep-alive command to ensure the shell process + // stays alive even if the main command exits (e.g. spawns a detached child). + // This ensures we always have a valid PID for cleanup. + if (isWindows && shouldRunInBackground) { + // Remove trailing & if present to avoid syntax errors (e.g. "cmd & & ping") + let cmd = finalCommand.trim(); + while (cmd.endsWith('&')) { + cmd = cmd.slice(0, -1).trim(); + } + finalCommand = cmd + ' & ping -n 86400 127.0.0.1 >nul'; + } + // pgrep is not available on Windows, so we can't get background PIDs const commandToExecute = isWindows ? finalCommand @@ -206,7 +235,7 @@ export class ShellToolInvocation extends BaseToolInvocation< lastUpdateTime = Date.now(); } }, - signal, + abortController.signal, this.config.getShouldUseNodePtyShell(), shellExecutionConfig ?? {}, ); @@ -215,7 +244,34 @@ export class ShellToolInvocation extends BaseToolInvocation< setPidCallback(pid); } - const result = await resultPromise; + let result: ShellExecutionResult; + if (shouldRunInBackground && isWindows) { + // For Windows background tasks, we wait a short time to catch immediate errors. + // If it's still running, we return early. + const startupDelay = 1000; + const raceResult = await Promise.race([ + resultPromise, + new Promise((resolve) => + setTimeout(() => resolve(null), startupDelay), + ), + ]); + + if (raceResult === null) { + // Timeout reached, process is still running. + const pidMsg = pid ? ` PID: ${pid}` : ''; + const winHint = isWindows + ? ' (Note: Use taskkill /F /T /PID to stop)' + : ''; + return { + llmContent: `Background command started.${pidMsg}${winHint}`, + returnDisplay: `Background command started.${pidMsg}${winHint}`, + }; + } else { + result = raceResult; + } + } else { + result = await resultPromise; + } const backgroundPIDs: number[] = []; if (os.platform() !== 'win32') { @@ -321,6 +377,8 @@ export class ShellToolInvocation extends BaseToolInvocation< ...executionError, }; } finally { + clearTimeout(timeoutId); + signal.removeEventListener('abort', onAbort); if (fs.existsSync(tempFilePath)) { fs.unlinkSync(tempFilePath); } @@ -454,6 +512,11 @@ export class ShellTool extends BaseDeclarativeTool< description: '(OPTIONAL) The absolute path of the directory to run the command in. If not provided, the project root directory is used. Must be a directory within the workspace and must already exist.', }, + timeout: { + type: 'number', + description: + '(OPTIONAL) The timeout in milliseconds for the command. If not provided, a default timeout (1 hour) is applied.', + }, }, required: ['command', 'is_background'], }, @@ -478,6 +541,9 @@ export class ShellTool extends BaseDeclarativeTool< if (!params.command.trim()) { return 'Command cannot be empty.'; } + if (params.timeout !== undefined && params.timeout <= 0) { + return 'Timeout must be a positive number.'; + } if (getCommandRoots(params.command).length === 0) { return 'Could not identify command root to obtain permission from user.'; } From 403fd061171be14d6e738e600f394b4f0a663dc5 Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Thu, 4 Dec 2025 15:51:08 +0800 Subject: [PATCH 02/65] chore: update .gitignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index bf3aa4211..2c3156b96 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,3 @@ gha-creds-*.json # Log files patch_output.log - -# test files -demo-app \ No newline at end of file From 4c69d536acfe9b04a08695d5dd1804f9712ccafc Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Fri, 5 Dec 2025 10:47:06 +0800 Subject: [PATCH 03/65] test: fix shell tool tests by updating pid expectation and AbortSignal matching --- .../services/shellExecutionService.test.ts | 2 +- packages/core/src/tools/shell.test.ts | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/core/src/services/shellExecutionService.test.ts b/packages/core/src/services/shellExecutionService.test.ts index 4dfc48918..c5a6e0776 100644 --- a/packages/core/src/services/shellExecutionService.test.ts +++ b/packages/core/src/services/shellExecutionService.test.ts @@ -589,7 +589,7 @@ describe('ShellExecutionService child_process fallback', () => { expect(result.error).toBeNull(); expect(result.aborted).toBe(false); expect(result.output).toBe('file1.txt\na warning'); - expect(handle.pid).toBe(undefined); + expect(handle.pid).toBe(12345); expect(onOutputEventMock).toHaveBeenCalledWith({ type: 'data', diff --git a/packages/core/src/tools/shell.test.ts b/packages/core/src/tools/shell.test.ts index 043ab0c64..bde508370 100644 --- a/packages/core/src/tools/shell.test.ts +++ b/packages/core/src/tools/shell.test.ts @@ -210,7 +210,7 @@ describe('ShellTool', () => { wrappedCommand, '/test/dir', expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -237,7 +237,7 @@ describe('ShellTool', () => { wrappedCommand, expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -262,7 +262,7 @@ describe('ShellTool', () => { wrappedCommand, expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -287,7 +287,7 @@ describe('ShellTool', () => { wrappedCommand, expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -312,7 +312,7 @@ describe('ShellTool', () => { wrappedCommand, '/test/dir/subdir', expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -340,7 +340,7 @@ describe('ShellTool', () => { 'dir', '/test/dir', expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -433,7 +433,7 @@ describe('ShellTool', () => { expect(summarizer.summarizeToolOutput).toHaveBeenCalledWith( expect.any(String), mockConfig.getGeminiClient(), - mockAbortSignal, + expect.any(AbortSignal), 1000, ); expect(result.llmContent).toBe('summarized output'); @@ -542,7 +542,7 @@ describe('ShellTool', () => { ), expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -572,7 +572,7 @@ describe('ShellTool', () => { ), expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -602,7 +602,7 @@ describe('ShellTool', () => { ), expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -631,7 +631,7 @@ describe('ShellTool', () => { expect.stringContaining('npm install'), expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -660,7 +660,7 @@ describe('ShellTool', () => { expect.stringContaining('git commit'), expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -690,7 +690,7 @@ describe('ShellTool', () => { ), expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -726,7 +726,7 @@ describe('ShellTool', () => { expect.stringContaining('git commit -m "Initial commit"'), expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); @@ -763,7 +763,7 @@ describe('ShellTool', () => { ), expect.any(String), expect.any(Function), - mockAbortSignal, + expect.any(AbortSignal), false, {}, ); From 28d178b5c1878570fa193e5342675b1f251db0ce Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Tue, 9 Dec 2025 11:24:30 +0800 Subject: [PATCH 04/65] fix: handle windows background execution errors and add tests --- .../src/services/shellExecutionService.ts | 67 ++++++++----------- packages/core/src/tools/shell.test.ts | 29 ++++++++ packages/core/src/tools/shell.ts | 38 +++++++++-- 3 files changed, 90 insertions(+), 44 deletions(-) diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index a9ad273bf..e501e6ecd 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -108,47 +108,38 @@ export class ShellExecutionService { private static activePtys = new Map(); private static activeChildProcesses = new Set(); - static { - const cleanup = () => { - // Cleanup PTYs - for (const [pid, pty] of this.activePtys) { - try { - if (os.platform() === 'win32') { - pty.ptyProcess.kill(); - } else { - process.kill(-pid, 'SIGKILL'); - } - } catch { - // ignore + static cleanup() { + // Cleanup PTYs + for (const [pid, pty] of this.activePtys) { + try { + if (os.platform() === 'win32') { + pty.ptyProcess.kill(); + } else { + process.kill(-pid, 'SIGKILL'); } + } catch { + // ignore } - - // Cleanup child processes - for (const pid of this.activeChildProcesses) { - try { - if (os.platform() === 'win32') { - spawnSync('taskkill', ['/pid', pid.toString(), '/f', '/t']); - } else { - process.kill(-pid, 'SIGKILL'); - } - } catch { - // ignore - } - } - }; - - process.on('exit', cleanup); - - // Ensure cleanup happens on SIGINT/SIGTERM - const signalHandler = () => { - process.exit(); - }; - - // We only attach these if we are in a node environment where we can control the process - if (typeof process !== 'undefined' && process.on) { - process.on('SIGINT', signalHandler); - process.on('SIGTERM', signalHandler); } + + // Cleanup child processes + for (const pid of this.activeChildProcesses) { + try { + if (os.platform() === 'win32') { + spawnSync('taskkill', ['/pid', pid.toString(), '/f', '/t']); + } else { + process.kill(-pid, 'SIGKILL'); + } + } catch { + // ignore + } + } + } + + static { + process.on('exit', () => { + ShellExecutionService.cleanup(); + }); } /** diff --git a/packages/core/src/tools/shell.test.ts b/packages/core/src/tools/shell.test.ts index bde508370..b431f494f 100644 --- a/packages/core/src/tools/shell.test.ts +++ b/packages/core/src/tools/shell.test.ts @@ -831,4 +831,33 @@ describe('ShellTool', () => { expect(shellTool.description).toMatchSnapshot(); }); }); + + describe('Windows background execution', () => { + it('should detect immediate failure in Windows background task', async () => { + vi.mocked(os.platform).mockReturnValue('win32'); + const mockAbortSignal = new AbortController().signal; + + const invocation = shellTool.build({ + command: 'invalid_command', + is_background: true, + }); + + const promise = invocation.execute(mockAbortSignal); + + // Wait a tick to ensure mockShellOutputCallback is assigned + await new Promise((resolve) => setTimeout(resolve, 0)); + + if (mockShellOutputCallback) { + mockShellOutputCallback({ + type: 'data', + chunk: + "'invalid_command' is not recognized as an internal or external command,\r\noperable program or batch file.\r\n", + }); + } + + const result = await promise; + expect(result.error).toBeDefined(); + expect(result.llmContent).toContain('Command failed to start'); + }); + }); }); diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index b303b0fac..4ee7e79c6 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -194,16 +194,16 @@ export class ShellToolInvocation extends BaseToolInvocation< commandToExecute, cwd, (event: ShellOutputEvent) => { - if (!updateOutput) { - return; - } - let shouldUpdate = false; switch (event.type) { case 'data': if (isBinaryStream) break; - cumulativeOutput = event.chunk; + if (typeof cumulativeOutput === 'string') { + cumulativeOutput += event.chunk; + } else { + cumulativeOutput = event.chunk; + } shouldUpdate = true; break; case 'binary_detected': @@ -226,7 +226,7 @@ export class ShellToolInvocation extends BaseToolInvocation< } } - if (shouldUpdate) { + if (shouldUpdate && updateOutput) { updateOutput( typeof cumulativeOutput === 'string' ? cumulativeOutput @@ -258,6 +258,32 @@ export class ShellToolInvocation extends BaseToolInvocation< if (raceResult === null) { // Timeout reached, process is still running. + // throw new Error(`DEBUG: raceResult is null. Output: ${JSON.stringify(cumulativeOutput)}`); + + // Check for common Windows error messages in the output + const outputStr = + typeof cumulativeOutput === 'string' + ? cumulativeOutput + : JSON.stringify(cumulativeOutput); + console.log('DEBUG: outputStr:', outputStr); + const errorPatterns = [ + 'is not recognized as an internal or external command', + 'The system cannot find the path specified', + 'Access is denied', + ]; + + if (errorPatterns.some((pattern) => outputStr.includes(pattern))) { + abortController.abort(); + return { + llmContent: `Command failed to start: ${outputStr}`, + returnDisplay: `Command failed to start: ${outputStr}`, + error: { + type: ToolErrorType.EXECUTION_FAILED, + message: `Command failed to start: ${outputStr}`, + }, + }; + } + const pidMsg = pid ? ` PID: ${pid}` : ''; const winHint = isWindows ? ' (Note: Use taskkill /F /T /PID to stop)' From 6fc09a82fb6f3cc9e7dd50ea706bddd6e346724b Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Tue, 9 Dec 2025 13:33:42 +0800 Subject: [PATCH 05/65] fix: use && for windows background keep-alive ping and add test --- packages/core/src/tools/shell.test.ts | 35 +++++++++++++++++++++++++++ packages/core/src/tools/shell.ts | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/core/src/tools/shell.test.ts b/packages/core/src/tools/shell.test.ts index b431f494f..b98e71584 100644 --- a/packages/core/src/tools/shell.test.ts +++ b/packages/core/src/tools/shell.test.ts @@ -833,6 +833,41 @@ describe('ShellTool', () => { }); describe('Windows background execution', () => { + it('should append keep-alive ping with && on Windows for background tasks', async () => { + vi.mocked(os.platform).mockReturnValue('win32'); + const mockAbortSignal = new AbortController().signal; + + const invocation = shellTool.build({ + command: 'npm start', + is_background: true, + }); + + const promise = invocation.execute(mockAbortSignal); + + // Simulate immediate success (process started) + resolveExecutionPromise({ + rawOutput: Buffer.from(''), + output: '', + exitCode: 0, + signal: null, + error: null, + aborted: false, + pid: 12345, + executionMethod: 'child_process', + }); + + await promise; + + expect(mockShellExecutionService).toHaveBeenCalledWith( + expect.stringContaining('npm start && ping -n 86400 127.0.0.1 >nul'), + expect.any(String), + expect.any(Function), + expect.any(AbortSignal), + false, + {}, + ); + }); + it('should detect immediate failure in Windows background task', async () => { vi.mocked(os.platform).mockReturnValue('win32'); const mockAbortSignal = new AbortController().signal; diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index 55bc4df02..8d7610d46 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -174,7 +174,7 @@ export class ShellToolInvocation extends BaseToolInvocation< while (cmd.endsWith('&')) { cmd = cmd.slice(0, -1).trim(); } - finalCommand = cmd + ' & ping -n 86400 127.0.0.1 >nul'; + finalCommand = cmd + ' && ping -n 86400 127.0.0.1 >nul'; } // pgrep is not available on Windows, so we can't get background PIDs From 16939c0bc8cbe2ee0e7743e6fd96fced51f35a10 Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Wed, 10 Dec 2025 13:49:51 +0800 Subject: [PATCH 06/65] Refactor ShellTool: remove ping hack and timeout, optimize cleanup --- .../src/services/shellExecutionService.ts | 26 +++++++++++----- packages/core/src/tools/shell.test.ts | 6 ++-- packages/core/src/tools/shell.ts | 31 +++---------------- 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index e501e6ecd..4a638c620 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -123,15 +123,25 @@ export class ShellExecutionService { } // Cleanup child processes - for (const pid of this.activeChildProcesses) { - try { - if (os.platform() === 'win32') { - spawnSync('taskkill', ['/pid', pid.toString(), '/f', '/t']); - } else { - process.kill(-pid, 'SIGKILL'); + if (os.platform() === 'win32') { + if (this.activeChildProcesses.size > 0) { + try { + const args = ['/f', '/t']; + for (const pid of this.activeChildProcesses) { + args.push('/pid', pid.toString()); + } + spawnSync('taskkill', args); + } catch { + // ignore + } + } + } else { + for (const pid of this.activeChildProcesses) { + try { + process.kill(-pid, 'SIGKILL'); + } catch { + // ignore } - } catch { - // ignore } } } diff --git a/packages/core/src/tools/shell.test.ts b/packages/core/src/tools/shell.test.ts index b98e71584..3484c53b2 100644 --- a/packages/core/src/tools/shell.test.ts +++ b/packages/core/src/tools/shell.test.ts @@ -833,12 +833,12 @@ describe('ShellTool', () => { }); describe('Windows background execution', () => { - it('should append keep-alive ping with && on Windows for background tasks', async () => { + it('should clean up trailing ampersand on Windows for background tasks', async () => { vi.mocked(os.platform).mockReturnValue('win32'); const mockAbortSignal = new AbortController().signal; const invocation = shellTool.build({ - command: 'npm start', + command: 'npm start &', is_background: true, }); @@ -859,7 +859,7 @@ describe('ShellTool', () => { await promise; expect(mockShellExecutionService).toHaveBeenCalledWith( - expect.stringContaining('npm start && ping -n 86400 127.0.0.1 >nul'), + 'npm start', expect.any(String), expect.any(Function), expect.any(AbortSignal), diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index 8d7610d46..a886010d9 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -49,7 +49,6 @@ export interface ShellToolParams { is_background: boolean; description?: string; directory?: string; - timeout?: number; } export class ShellToolInvocation extends BaseToolInvocation< @@ -138,14 +137,6 @@ export class ShellToolInvocation extends BaseToolInvocation< .toString('hex')}.tmp`; const tempFilePath = path.join(os.tmpdir(), tempFileName); - const timeoutMs = this.params.timeout ?? 3600000; - const abortController = new AbortController(); - const onAbort = () => abortController.abort(); - signal.addEventListener('abort', onAbort); - const timeoutId = setTimeout(() => { - abortController.abort(); - }, timeoutMs); - try { // Add co-author to git commit commands const processedCommand = this.addCoAuthorToGitCommit(strippedCommand); @@ -165,16 +156,15 @@ export class ShellToolInvocation extends BaseToolInvocation< finalCommand = finalCommand.trim() + ' &'; } - // On Windows, append a keep-alive command to ensure the shell process - // stays alive even if the main command exits (e.g. spawns a detached child). - // This ensures we always have a valid PID for cleanup. + // On Windows, we rely on the race logic below to handle background tasks. + // We just ensure the command string is clean. if (isWindows && shouldRunInBackground) { - // Remove trailing & if present to avoid syntax errors (e.g. "cmd & & ping") let cmd = finalCommand.trim(); + // Remove trailing & (common Linux habit, invalid on Windows at end of line) while (cmd.endsWith('&')) { cmd = cmd.slice(0, -1).trim(); } - finalCommand = cmd + ' && ping -n 86400 127.0.0.1 >nul'; + finalCommand = cmd; } // pgrep is not available on Windows, so we can't get background PIDs @@ -239,7 +229,7 @@ export class ShellToolInvocation extends BaseToolInvocation< lastUpdateTime = Date.now(); } }, - abortController.signal, + signal, this.config.getShouldUseNodePtyShell(), shellExecutionConfig ?? {}, ); @@ -277,7 +267,6 @@ export class ShellToolInvocation extends BaseToolInvocation< ]; if (errorPatterns.some((pattern) => outputStr.includes(pattern))) { - abortController.abort(); return { llmContent: `Command failed to start: ${outputStr}`, returnDisplay: `Command failed to start: ${outputStr}`, @@ -407,8 +396,6 @@ export class ShellToolInvocation extends BaseToolInvocation< ...executionError, }; } finally { - clearTimeout(timeoutId); - signal.removeEventListener('abort', onAbort); if (fs.existsSync(tempFilePath)) { fs.unlinkSync(tempFilePath); } @@ -542,11 +529,6 @@ export class ShellTool extends BaseDeclarativeTool< description: '(OPTIONAL) The absolute path of the directory to run the command in. If not provided, the project root directory is used. Must be a directory within the workspace and must already exist.', }, - timeout: { - type: 'number', - description: - '(OPTIONAL) The timeout in milliseconds for the command. If not provided, a default timeout (1 hour) is applied.', - }, }, required: ['command', 'is_background'], }, @@ -571,9 +553,6 @@ export class ShellTool extends BaseDeclarativeTool< if (!params.command.trim()) { return 'Command cannot be empty.'; } - if (params.timeout !== undefined && params.timeout <= 0) { - return 'Timeout must be a positive number.'; - } if (getCommandRoots(params.command).length === 0) { return 'Could not identify command root to obtain permission from user.'; } From 574d89da14cf50ad458b76850c7bec5069063ef7 Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Fri, 12 Dec 2025 17:03:04 +0800 Subject: [PATCH 07/65] Refactor ShellExecutionService cleanup to use strategy pattern --- .../src/services/shellExecutionService.ts | 70 ++++++++++++------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index 4a638c620..f78094329 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -98,6 +98,48 @@ const getFullBufferText = (terminal: pkg.Terminal): string => { return lines.join('\n').trimEnd(); }; +interface ProcessCleanupStrategy { + killPty(pid: number, pty: ActivePty): void; + killChildProcesses(pids: Set): void; +} + +const windowsStrategy: ProcessCleanupStrategy = { + killPty: (_pid, pty) => { + pty.ptyProcess.kill(); + }, + killChildProcesses: (pids) => { + if (pids.size > 0) { + try { + const args = ['/f', '/t']; + for (const pid of pids) { + args.push('/pid', pid.toString()); + } + spawnSync('taskkill', args); + } catch { + // ignore + } + } + }, +}; + +const posixStrategy: ProcessCleanupStrategy = { + killPty: (pid, _pty) => { + process.kill(-pid, 'SIGKILL'); + }, + killChildProcesses: (pids) => { + for (const pid of pids) { + try { + process.kill(-pid, 'SIGKILL'); + } catch { + // ignore + } + } + }, +}; + +const cleanupStrategy = + os.platform() === 'win32' ? windowsStrategy : posixStrategy; + /** * A centralized service for executing shell commands with robust process * management, cross-platform compatibility, and streaming output capabilities. @@ -112,38 +154,14 @@ export class ShellExecutionService { // Cleanup PTYs for (const [pid, pty] of this.activePtys) { try { - if (os.platform() === 'win32') { - pty.ptyProcess.kill(); - } else { - process.kill(-pid, 'SIGKILL'); - } + cleanupStrategy.killPty(pid, pty); } catch { // ignore } } // Cleanup child processes - if (os.platform() === 'win32') { - if (this.activeChildProcesses.size > 0) { - try { - const args = ['/f', '/t']; - for (const pid of this.activeChildProcesses) { - args.push('/pid', pid.toString()); - } - spawnSync('taskkill', args); - } catch { - // ignore - } - } - } else { - for (const pid of this.activeChildProcesses) { - try { - process.kill(-pid, 'SIGKILL'); - } catch { - // ignore - } - } - } + cleanupStrategy.killChildProcesses(this.activeChildProcesses); } static { From b272ac0119e930c568eb1869770dfa0b9d3b9204 Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Fri, 12 Dec 2025 17:47:03 +0800 Subject: [PATCH 08/65] Fix: Make cleanup strategy dynamic to support testing mocks --- packages/core/src/services/shellExecutionService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index f78094329..853a4c89f 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -137,7 +137,7 @@ const posixStrategy: ProcessCleanupStrategy = { }, }; -const cleanupStrategy = +const getCleanupStrategy = () => os.platform() === 'win32' ? windowsStrategy : posixStrategy; /** @@ -151,17 +151,18 @@ export class ShellExecutionService { private static activeChildProcesses = new Set(); static cleanup() { + const strategy = getCleanupStrategy(); // Cleanup PTYs for (const [pid, pty] of this.activePtys) { try { - cleanupStrategy.killPty(pid, pty); + strategy.killPty(pid, pty); } catch { // ignore } } // Cleanup child processes - cleanupStrategy.killChildProcesses(this.activeChildProcesses); + strategy.killChildProcesses(this.activeChildProcesses); } static { From 8673426d5c2847a1bf4a3392ce7fa2816263e077 Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Tue, 16 Dec 2025 10:26:20 +0800 Subject: [PATCH 09/65] fix(core): use current chunk for shell output update instead of cumulative --- packages/core/src/tools/shell.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index a886010d9..194de4e86 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -193,11 +193,7 @@ export class ShellToolInvocation extends BaseToolInvocation< switch (event.type) { case 'data': if (isBinaryStream) break; - if (typeof cumulativeOutput === 'string') { - cumulativeOutput += event.chunk; - } else { - cumulativeOutput = event.chunk; - } + cumulativeOutput = event.chunk; shouldUpdate = true; break; case 'binary_detected': From 6ca54beba2b92930328a5c6d5a01d4ad98c04d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E4=BC=9F=E5=85=89?= Date: Wed, 17 Dec 2025 13:38:38 +0800 Subject: [PATCH 10/65] feat: Optimize the issue where an error message indicating unfriendliness occurs after executing the ideinstall command in the sandbox environment --- packages/cli/src/ui/IdeIntegrationNudge.tsx | 17 ++++++++++------- packages/cli/src/ui/commands/ideCommand.ts | 11 +++++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/ui/IdeIntegrationNudge.tsx b/packages/cli/src/ui/IdeIntegrationNudge.tsx index 8ab350064..d6cbc11f5 100644 --- a/packages/cli/src/ui/IdeIntegrationNudge.tsx +++ b/packages/cli/src/ui/IdeIntegrationNudge.tsx @@ -38,6 +38,7 @@ export function IdeIntegrationNudge({ ); const { displayName: ideName } = ide; + const isInSandbox = !!process.env['SANDBOX']; // Assume extension is already installed if the env variables are set. const isExtensionPreInstalled = !!process.env['QWEN_CODE_IDE_SERVER_PORT'] && @@ -70,13 +71,15 @@ export function IdeIntegrationNudge({ }, ]; - const installText = isExtensionPreInstalled - ? `If you select Yes, the CLI will have access to your open files and display diffs directly in ${ - ideName ?? 'your editor' - }.` - : `If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${ - ideName ?? 'your editor' - }.`; + const installText = isInSandbox + ? `Note: In sandbox environments, IDE integration requires manual setup on the host system. If you select Yes, you'll receive instructions on how to set this up.` + : isExtensionPreInstalled + ? `If you select Yes, the CLI will have access to your open files and display diffs directly in ${ + ideName ?? 'your editor' + }.` + : `If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${ + ideName ?? 'your editor' + }.`; return ( => { kind: CommandKind.BUILT_IN, action: async (context) => { const installer = getIdeInstaller(currentIDE); + const isSandBox = !!process.env['SANDBOX']; + if (isSandBox) { + context.ui.addItem( + { + type: 'info', + text: `IDE integration needs to be installed on the host. If you have already installed it, you can directly connect the ide`, + }, + Date.now(), + ); + return; + } if (!installer) { context.ui.addItem( { From b3b2bc6ad5bc9e3bb08c9e31cb959d2f8244145f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E4=BC=9F=E5=85=89?= Date: Fri, 19 Dec 2025 10:39:05 +0800 Subject: [PATCH 11/65] =?UTF-8?q?feat:=20=E5=85=BC=E5=AE=B9=E5=AE=BF?= =?UTF-8?q?=E4=B8=BB=E6=9C=BA=E5=9C=A8=E4=B8=8D=E5=90=8Cide=E4=B8=8A?= =?UTF-8?q?=E7=9A=84instal=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/ui/commands/ideCommand.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/ui/commands/ideCommand.ts b/packages/cli/src/ui/commands/ideCommand.ts index d05ce8127..556fa08ea 100644 --- a/packages/cli/src/ui/commands/ideCommand.ts +++ b/packages/cli/src/ui/commands/ideCommand.ts @@ -203,10 +203,23 @@ export const ideCommand = async (): Promise => { return; } if (!installer) { + const ideName = ideClient.getDetectedIdeDisplayName(); + const isVSCode = currentIDE.name === 'vscode'; + let type: 'error' | 'info' = 'error'; + let message: string; + if (isVSCode) { + // VS Code + message = `No installer is available for ${ideName}. Please install the '${QWEN_CODE_COMPANION_EXTENSION_NAME}' extension manually from the marketplace.`; + } else { + // NO VS Code + type = 'info'; + message = `Automatic installation is not supported for ${ideName}. Please install '${QWEN_CODE_COMPANION_EXTENSION_NAME}' in VS Code. If you have installed it before, please ignore the reminder and directly connect the ide extension`; + } + context.ui.addItem( { - type: 'error', - text: `No installer is available for ${ideClient.getDetectedIdeDisplayName()}. Please install the '${QWEN_CODE_COMPANION_EXTENSION_NAME}' extension manually from the marketplace.`, + type, + text: message, }, Date.now(), ); From 34d8dbf9b2530081db9e7612de0a8112902c77f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E4=BC=9F=E5=85=89?= Date: Fri, 19 Dec 2025 11:07:33 +0800 Subject: [PATCH 12/65] =?UTF-8?q?feat:=20=E5=85=BC=E5=AE=B9=E5=AE=BF?= =?UTF-8?q?=E4=B8=BB=E6=9C=BA=E5=9C=A8=E4=B8=8D=E5=90=8Cide=E4=B8=8A?= =?UTF-8?q?=E7=9A=84instal=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/ui/commands/ideCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/ui/commands/ideCommand.ts b/packages/cli/src/ui/commands/ideCommand.ts index 556fa08ea..2440ca852 100644 --- a/packages/cli/src/ui/commands/ideCommand.ts +++ b/packages/cli/src/ui/commands/ideCommand.ts @@ -213,7 +213,7 @@ export const ideCommand = async (): Promise => { } else { // NO VS Code type = 'info'; - message = `Automatic installation is not supported for ${ideName}. Please install '${QWEN_CODE_COMPANION_EXTENSION_NAME}' in VS Code. If you have installed it before, please ignore the reminder and directly connect the ide extension`; + message = `Automatic installation is not supported for ${ideName}. Please install the extension manually or install '${QWEN_CODE_COMPANION_EXTENSION_NAME}' in VS Code. If you have installed it before, please ignore the reminder and directly connect the ide extension`; } context.ui.addItem( From 43e0815def04170de30a7c78338158ca59371adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E4=BC=9F=E5=85=89?= Date: Mon, 22 Dec 2025 11:22:51 +0800 Subject: [PATCH 13/65] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E9=93=BE?= =?UTF-8?q?=E6=8E=A5ide=E4=B9=8B=E5=89=8D=E7=9A=84=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E9=80=BB=E8=BE=91,=E6=A3=80=E6=B5=8B=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E8=BF=87ide=E6=89=A9=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/ui/AppContainer.tsx | 7 ++++++- packages/cli/src/ui/IdeIntegrationNudge.tsx | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index e70c0446b..6c9242c02 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -938,7 +938,12 @@ export const AppContainer = (props: AppContainerProps) => { const handleIdePromptComplete = useCallback( (result: IdeIntegrationNudgeResult) => { if (result.userSelection === 'yes') { - handleSlashCommand('/ide install'); + // Check whether the extension has been pre-installed + if (result.isExtensionPreInstalled) { + handleSlashCommand('/ide enable'); + } else { + handleSlashCommand('/ide install'); + } settings.setValue(SettingScope.User, 'ide.hasSeenNudge', true); } else if (result.userSelection === 'dismiss') { settings.setValue(SettingScope.User, 'ide.hasSeenNudge', true); diff --git a/packages/cli/src/ui/IdeIntegrationNudge.tsx b/packages/cli/src/ui/IdeIntegrationNudge.tsx index d6cbc11f5..a53f59b98 100644 --- a/packages/cli/src/ui/IdeIntegrationNudge.tsx +++ b/packages/cli/src/ui/IdeIntegrationNudge.tsx @@ -74,9 +74,9 @@ export function IdeIntegrationNudge({ const installText = isInSandbox ? `Note: In sandbox environments, IDE integration requires manual setup on the host system. If you select Yes, you'll receive instructions on how to set this up.` : isExtensionPreInstalled - ? `If you select Yes, the CLI will have access to your open files and display diffs directly in ${ - ideName ?? 'your editor' - }.` + ? `The IDE extension appears to be already installed. If you select Yes, the CLI will connect to your ${ + ideName ?? 'editor' + } and have access to your open files and display diffs directly.` : `If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${ ideName ?? 'your editor' }.`; From 5779f7ab1d037a430a69921f5a2cad056d82ae52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=BE=E7=A6=BB?= Date: Tue, 23 Dec 2025 17:20:12 +0800 Subject: [PATCH 14/65] project initialize --- packages/sdk-java/.editorconfig | 24 ++++ packages/sdk-java/.gitignore | 13 ++ packages/sdk-java/checkstyle.xml | 131 ++++++++++++++++++ packages/sdk-java/pom.xml | 65 +++++++++ .../code/cli/transport/PermissionMode.java | 27 ++++ .../code/cli/transport/ProcessTransport.java | 9 ++ .../code/cli/transport/TransportOptions.java | 23 +++ .../cli/transport/PermissionModeTest.java | 16 +++ 8 files changed, 308 insertions(+) create mode 100644 packages/sdk-java/.editorconfig create mode 100644 packages/sdk-java/.gitignore create mode 100644 packages/sdk-java/checkstyle.xml create mode 100644 packages/sdk-java/pom.xml create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/PermissionMode.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java create mode 100644 packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java diff --git a/packages/sdk-java/.editorconfig b/packages/sdk-java/.editorconfig new file mode 100644 index 000000000..53a4241f9 --- /dev/null +++ b/packages/sdk-java/.editorconfig @@ -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 diff --git a/packages/sdk-java/.gitignore b/packages/sdk-java/.gitignore new file mode 100644 index 000000000..bb45e2790 --- /dev/null +++ b/packages/sdk-java/.gitignore @@ -0,0 +1,13 @@ +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +# Mac +.DS_Store + +# Maven +log/ +target/ + diff --git a/packages/sdk-java/checkstyle.xml b/packages/sdk-java/checkstyle.xml new file mode 100644 index 000000000..fa316ec72 --- /dev/null +++ b/packages/sdk-java/checkstyle.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml new file mode 100644 index 000000000..0438427ce --- /dev/null +++ b/packages/sdk-java/pom.xml @@ -0,0 +1,65 @@ + + 4.0.0 + com.alibaba + qwencode-sdk-java + jar + 0.0.1 + qwencode-sdk-java + https://maven.apache.org + + + Apache 2 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + https://github.com/QwenLM/qwen-code + scm:git:https://github.com/QwenLM/qwen-code.git + + + 1.8 + 1.8 + UTF-8 + 3.6.0 + + + + junit + junit + 4.13.2 + test + + + ch.qos.logback + logback-classic + 1.5.23 + compile + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle-maven-plugin.version} + + checkstyle.xml + + + + + check + + + + + + + + diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/PermissionMode.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/PermissionMode.java new file mode 100644 index 000000000..3db5782c6 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/PermissionMode.java @@ -0,0 +1,27 @@ +package com.alibaba.qwen.code.cli.transport; + +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); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java new file mode 100644 index 000000000..73584ef21 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java @@ -0,0 +1,9 @@ +package com.alibaba.qwen.code.cli.transport; + +public class ProcessTransport { + Process process; + + public ProcessTransport(Process process) { + this.process = process; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java new file mode 100644 index 000000000..cef5b1813 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java @@ -0,0 +1,23 @@ +package com.alibaba.qwen.code.cli.transport; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class TransportOptions { + private String pathToQwenExecutable; + private String cwd; + private String model; + private PermissionMode permissionMode; + private Map env; + private Object abortController; // AbortController in JavaScript does not have a direct Java equivalent + private Boolean debug; + private Consumer stderr; // Equivalent to (message: string) => void + private String logLevel; // Can be 'debug', 'info', 'warn', or 'error' + private Integer maxSessionTurns; + private List coreTools; + private List excludeTools; + private List allowedTools; + private String authType; + private Boolean includePartialMessages; +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java new file mode 100644 index 000000000..31cf692fc --- /dev/null +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java @@ -0,0 +1,16 @@ +package com.alibaba.qwen.code.cli.transport; + +import org.junit.Assert; +import org.junit.Test; + +public class PermissionModeTest { + + @Test + public void shouldBeReturnQwenPermissionModeValue() { + Assert.assertEquals("default", PermissionMode.DEFAULT.getValue()); + Assert.assertEquals("plan", PermissionMode.PLAN.getValue()); + Assert.assertEquals("auto-edit", PermissionMode.AUTO_EDIT.getValue()); + Assert.assertEquals("yolo", PermissionMode.YOLO.getValue()); + } + +} From 2ef8b6f350c60a3cf3ea7c754fe256d68e6604ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=BE=E7=A6=BB?= Date: Tue, 23 Dec 2025 17:44:28 +0800 Subject: [PATCH 15/65] ProcessTransport stru init --- .../code/cli/transport/ProcessTransport.java | 5 +- .../code/cli/transport/TransportOptions.java | 120 ++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java index 73584ef21..89f03f1b1 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java @@ -2,8 +2,9 @@ package com.alibaba.qwen.code.cli.transport; public class ProcessTransport { Process process; + TransportOptions transportOptions; - public ProcessTransport(Process process) { - this.process = process; + public ProcessTransport(TransportOptions transportOptions) { + this.transportOptions = transportOptions; } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java index cef5b1813..ca4cb83f9 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java @@ -20,4 +20,124 @@ public class TransportOptions { private List allowedTools; private String authType; private Boolean includePartialMessages; + + public String getPathToQwenExecutable() { + return pathToQwenExecutable; + } + + public void setPathToQwenExecutable(String pathToQwenExecutable) { + this.pathToQwenExecutable = pathToQwenExecutable; + } + + public String getCwd() { + return cwd; + } + + public void setCwd(String cwd) { + this.cwd = cwd; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public PermissionMode getPermissionMode() { + return permissionMode; + } + + public void setPermissionMode(PermissionMode permissionMode) { + this.permissionMode = permissionMode; + } + + public Map getEnv() { + return env; + } + + public void setEnv(Map env) { + this.env = env; + } + + public Object getAbortController() { + return abortController; + } + + public void setAbortController(Object abortController) { + this.abortController = abortController; + } + + public Boolean getDebug() { + return debug; + } + + public void setDebug(Boolean debug) { + this.debug = debug; + } + + public Consumer getStderr() { + return stderr; + } + + public void setStderr(Consumer stderr) { + this.stderr = stderr; + } + + public String getLogLevel() { + return logLevel; + } + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + public Integer getMaxSessionTurns() { + return maxSessionTurns; + } + + public void setMaxSessionTurns(Integer maxSessionTurns) { + this.maxSessionTurns = maxSessionTurns; + } + + public List getCoreTools() { + return coreTools; + } + + public void setCoreTools(List coreTools) { + this.coreTools = coreTools; + } + + public List getExcludeTools() { + return excludeTools; + } + + public void setExcludeTools(List excludeTools) { + this.excludeTools = excludeTools; + } + + public List getAllowedTools() { + return allowedTools; + } + + public void setAllowedTools(List allowedTools) { + this.allowedTools = allowedTools; + } + + public String getAuthType() { + return authType; + } + + public void setAuthType(String authType) { + this.authType = authType; + } + + public Boolean getIncludePartialMessages() { + return includePartialMessages; + } + + public void setIncludePartialMessages(Boolean includePartialMessages) { + this.includePartialMessages = includePartialMessages; + } } From 24d11179d881022915f2fef908207419bb951ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=BE=E7=A6=BB?= Date: Tue, 23 Dec 2025 20:04:58 +0800 Subject: [PATCH 16/65] modify junit version to 5 and add org developers --- packages/sdk-java/pom.xml | 56 +++++++++++++++---- .../cli/transport/PermissionModeTest.java | 13 +++-- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 0438427ce..0ec6bb888 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -25,21 +25,33 @@ 1.8 UTF-8 3.6.0 + 5.14.1 + 1.5.23 + + + + + org.junit + junit-bom + pom + ${junit5.version} + import + + + - - junit - junit - 4.13.2 - test - ch.qos.logback logback-classic - 1.5.23 - compile + ${logback-classic.version} + provided + + + org.junit.jupiter + junit-jupiter + test - @@ -60,6 +72,30 @@ - + + + Alibaba Group + https://github.com/alibaba + + + + skyfire + skyfire + gengwei.gw(at)alibaba-inc.com + + Developer + Designer + + +8 + https://github.com/gwinthis + + + + + + central + https://central.sonatype.com/repository/maven-snapshots/ + + diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java index 31cf692fc..7707c5fda 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java @@ -1,16 +1,17 @@ package com.alibaba.qwen.code.cli.transport; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class PermissionModeTest { @Test public void shouldBeReturnQwenPermissionModeValue() { - Assert.assertEquals("default", PermissionMode.DEFAULT.getValue()); - Assert.assertEquals("plan", PermissionMode.PLAN.getValue()); - Assert.assertEquals("auto-edit", PermissionMode.AUTO_EDIT.getValue()); - Assert.assertEquals("yolo", PermissionMode.YOLO.getValue()); + assertEquals("default", PermissionMode.DEFAULT.getValue()); + assertEquals("plan", PermissionMode.PLAN.getValue()); + assertEquals("auto-edit", PermissionMode.AUTO_EDIT.getValue()); + assertEquals("yolo", PermissionMode.YOLO.getValue()); } } From e09bb5f5c0fd7dfd03dde229f036b09946d30943 Mon Sep 17 00:00:00 2001 From: skyfire Date: Tue, 23 Dec 2025 20:14:11 +0800 Subject: [PATCH 17/65] modify junit version to 5 and add org developers --- packages/sdk-java/pom.xml | 56 +++++++++++++++---- .../cli/transport/PermissionModeTest.java | 13 +++-- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 0438427ce..0ec6bb888 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -25,21 +25,33 @@ 1.8 UTF-8 3.6.0 + 5.14.1 + 1.5.23 + + + + + org.junit + junit-bom + pom + ${junit5.version} + import + + + - - junit - junit - 4.13.2 - test - ch.qos.logback logback-classic - 1.5.23 - compile + ${logback-classic.version} + provided + + + org.junit.jupiter + junit-jupiter + test - @@ -60,6 +72,30 @@ - + + + Alibaba Group + https://github.com/alibaba + + + + skyfire + skyfire + gengwei.gw(at)alibaba-inc.com + + Developer + Designer + + +8 + https://github.com/gwinthis + + + + + + central + https://central.sonatype.com/repository/maven-snapshots/ + + diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java index 31cf692fc..7707c5fda 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java @@ -1,16 +1,17 @@ package com.alibaba.qwen.code.cli.transport; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class PermissionModeTest { @Test public void shouldBeReturnQwenPermissionModeValue() { - Assert.assertEquals("default", PermissionMode.DEFAULT.getValue()); - Assert.assertEquals("plan", PermissionMode.PLAN.getValue()); - Assert.assertEquals("auto-edit", PermissionMode.AUTO_EDIT.getValue()); - Assert.assertEquals("yolo", PermissionMode.YOLO.getValue()); + assertEquals("default", PermissionMode.DEFAULT.getValue()); + assertEquals("plan", PermissionMode.PLAN.getValue()); + assertEquals("auto-edit", PermissionMode.AUTO_EDIT.getValue()); + assertEquals("yolo", PermissionMode.YOLO.getValue()); } } From 68628bf9520d450b7281ec40b319d9c133004d31 Mon Sep 17 00:00:00 2001 From: skyfire Date: Wed, 24 Dec 2025 20:45:17 +0800 Subject: [PATCH 18/65] add ProcessTransport --- packages/sdk-java/checkstyle.xml | 12 +- packages/sdk-java/pom.xml | 8 +- .../code/cli/transport/ProcessTransport.java | 10 - .../code/cli/transport/TransportOptions.java | 66 +++---- .../transport/process/ProcessTransport.java | 182 ++++++++++++++++++ .../process/TransportOptionsAdapter.java | 107 ++++++++++ .../process/ProcessTransportTest.java | 18 ++ 7 files changed, 347 insertions(+), 56 deletions(-) delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java create mode 100644 packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java diff --git a/packages/sdk-java/checkstyle.xml b/packages/sdk-java/checkstyle.xml index fa316ec72..c67c1319f 100644 --- a/packages/sdk-java/checkstyle.xml +++ b/packages/sdk-java/checkstyle.xml @@ -96,12 +96,12 @@ - - - - - - + + + + + + diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 0ec6bb888..1d1c7c25a 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -26,7 +26,7 @@ UTF-8 3.6.0 5.14.1 - 1.5.23 + 1.3.16 @@ -45,7 +45,11 @@ ch.qos.logback logback-classic ${logback-classic.version} - provided + + + org.apache.commons + commons-lang3 + 3.20.0 org.junit.jupiter diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java deleted file mode 100644 index 89f03f1b1..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/ProcessTransport.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.alibaba.qwen.code.cli.transport; - -public class ProcessTransport { - Process process; - TransportOptions transportOptions; - - public ProcessTransport(TransportOptions transportOptions) { - this.transportOptions = transportOptions; - } -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java index ca4cb83f9..b64df2ec6 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java @@ -2,24 +2,21 @@ package com.alibaba.qwen.code.cli.transport; import java.util.List; import java.util.Map; -import java.util.function.Consumer; -public class TransportOptions { +public class TransportOptions implements Cloneable { private String pathToQwenExecutable; private String cwd; private String model; private PermissionMode permissionMode; private Map env; - private Object abortController; // AbortController in JavaScript does not have a direct Java equivalent - private Boolean debug; - private Consumer stderr; // Equivalent to (message: string) => void - private String logLevel; // Can be 'debug', 'info', 'warn', or 'error' private Integer maxSessionTurns; private List coreTools; private List excludeTools; private List allowedTools; private String authType; private Boolean includePartialMessages; + private Long turnTimeoutMs; + private Long messageTimeoutMs; public String getPathToQwenExecutable() { return pathToQwenExecutable; @@ -61,38 +58,6 @@ public class TransportOptions { this.env = env; } - public Object getAbortController() { - return abortController; - } - - public void setAbortController(Object abortController) { - this.abortController = abortController; - } - - public Boolean getDebug() { - return debug; - } - - public void setDebug(Boolean debug) { - this.debug = debug; - } - - public Consumer getStderr() { - return stderr; - } - - public void setStderr(Consumer stderr) { - this.stderr = stderr; - } - - public String getLogLevel() { - return logLevel; - } - - public void setLogLevel(String logLevel) { - this.logLevel = logLevel; - } - public Integer getMaxSessionTurns() { return maxSessionTurns; } @@ -140,4 +105,29 @@ public class TransportOptions { public void setIncludePartialMessages(Boolean includePartialMessages) { this.includePartialMessages = includePartialMessages; } + + public Long getTurnTimeoutMs() { + return turnTimeoutMs; + } + + public void setTurnTimeoutMs(Long turnTimeoutMs) { + this.turnTimeoutMs = turnTimeoutMs; + } + + public Long getMessageTimeoutMs() { + return messageTimeoutMs; + } + + public void setMessageTimeoutMs(Long messageTimeoutMs) { + this.messageTimeoutMs = messageTimeoutMs; + } + + @Override + public TransportOptions clone() { + try { + return (TransportOptions) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java new file mode 100644 index 000000000..3adc1072b --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java @@ -0,0 +1,182 @@ +package com.alibaba.qwen.code.cli.transport.process; + +import com.alibaba.qwen.code.cli.transport.TransportOptions; + +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.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; + +public class ProcessTransport { + private static final Logger log = LoggerFactory.getLogger(ProcessTransport.class); + TransportOptionsAdapter transportOptionsAdapter; + + protected final Long turnTimeoutMs; + protected final Long messageTimeoutMs; + + protected Process process; + protected BufferedWriter processInput; + protected BufferedReader processOutput; + protected BufferedReader processError; + + public ProcessTransport(TransportOptions transportOptions) throws IOException { + this.transportOptionsAdapter = new TransportOptionsAdapter(transportOptions); + turnTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeoutMs(); + messageTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getMessageTimeoutMs(); + start(); + } + + protected void start() throws IOException { + 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(); + } + + 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(); + } + } + + public String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException { + return inputWaitForOneLine(message, turnTimeoutMs); + } + + private String inputWaitForOneLine(String message, long timeOutInMs) + throws IOException, TimeoutException, InterruptedException, ExecutionException { + inputNoWaitResponse(message); + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + return processOutput.readLine(); + } catch (IOException e) { + throw new ContextedRuntimeException("read line error", e) + .addContextValue("message", message); + } + }); + + try { + String line = future.get(timeOutInMs, TimeUnit.MILLISECONDS); + log.info("inputWaitForOneLine result: {}", line); + return line; + } catch (TimeoutException e) { + future.cancel(true); + log.warn("read message timeout {}, canceled readOneLine task", timeOutInMs, e); + throw e; + } catch (InterruptedException e) { + future.cancel(true); + log.warn("interrupted task, canceled task", e); + throw e; + } catch (ExecutionException e) { + future.cancel(true); + log.warn("the readOneLine task execute error", e); + throw e; + } + } + + public void inputWaitForMultiLine(String message, Function callBackFunction) throws IOException { + inputWaitForMultiLine(message, callBackFunction, turnTimeoutMs); + } + + private void inputWaitForMultiLine(String message, Function callBackFunction, long timeOutInMs) throws IOException { + log.debug("input message for multiLine: {}", message); + inputNoWaitResponse(message); + + CompletableFuture future = CompletableFuture.runAsync(() -> iterateOutput(callBackFunction)); + try { + future.get(timeOutInMs, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + future.cancel(true); + log.warn("read message timeout {}, canceled readMultiMessages task", timeOutInMs, e); + } catch (InterruptedException e) { + future.cancel(true); + log.warn("interrupted task, canceled task", e); + } catch (ExecutionException e) { + future.cancel(true); + log.warn("the readMultiMessages task execute error", e); + } catch (Exception e) { + future.cancel(true); + log.warn("other error"); + } + } + + public void inputNoWaitResponse(String message) throws IOException { + log.debug("input message to agent: {}", message); + processInput.write(message); + processInput.newLine(); + processInput.flush(); + } + + private void startErrorReading() { + CompletableFuture.runAsync(() -> { + try { + String line; + while ((line = processError.readLine()) != null) { + System.err.println("错误: " + line); + } + } catch (Exception e) { + System.err.println("错误: " + e.getMessage()); + } + }); + } + + private void iterateOutput(Function callBackFunction) { + CompletableFuture future = CompletableFuture.runAsync(() -> { + try { + for (String line = processOutput.readLine(); line != null; line = processOutput.readLine()) { + log.debug("read a message from agent {}", line); + if (callBackFunction.apply(line)) { + break; + } + } + } catch (IOException e) { + throw new RuntimeException("read process output error", e); + } + }); + + try { + future.get(messageTimeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + log.warn("read message 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); + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java new file mode 100644 index 000000000..e22f2fd27 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java @@ -0,0 +1,107 @@ +package com.alibaba.qwen.code.cli.transport.process; + +import com.alibaba.qwen.code.cli.transport.TransportOptions; + +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; + +class TransportOptionsAdapter { + TransportOptions transportOptions; + private static final Long DEFAULT_TURN_TIMEOUT_MS = 1000 * 60 * 30L; + private static final Long DEFAULT_MESSAGE_TIMEOUT_MS = 1000 * 60 * 3L; + + TransportOptionsAdapter(TransportOptions userTransportOptions) { + transportOptions = addDefaultTransportOptions(userTransportOptions); + } + + TransportOptions getHandledTransportOptions() { + return transportOptions; + } + + String getCwd() { + return transportOptions.getCwd(); + } + + String[] buildCommandArgs() { + List 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 (StringUtils.isNotBlank(transportOptions.getCwd())) { + args.add("--cwd"); + args.add(transportOptions.getCwd()); + } + + if (transportOptions.getPermissionMode() != null) { + args.add("--permission-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"); + } + return args.toArray(new String[] {}); + } + + private TransportOptions addDefaultTransportOptions(TransportOptions userTransportOptions) { + TransportOptions transportOptions = userTransportOptions.clone(); + + if (StringUtils.isBlank(transportOptions.getPathToQwenExecutable())) { + transportOptions.setPathToQwenExecutable("qwen"); + } + + if (StringUtils.isBlank(transportOptions.getCwd())) { + transportOptions.setCwd(new File("").getAbsolutePath()); + } + + Map env = new HashMap<>(System.getenv()); + Optional.ofNullable(transportOptions.getEnv()).ifPresent(env::putAll); + transportOptions.setEnv(env); + + if (transportOptions.getTurnTimeoutMs() == null) { + transportOptions.setTurnTimeoutMs(DEFAULT_TURN_TIMEOUT_MS); + } + + if (transportOptions.getMessageTimeoutMs() == null) { + transportOptions.setMessageTimeoutMs(DEFAULT_MESSAGE_TIMEOUT_MS); + } + return transportOptions; + } +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java new file mode 100644 index 000000000..af2a8d363 --- /dev/null +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java @@ -0,0 +1,18 @@ +package com.alibaba.qwen.code.cli.transport.process; + +import java.io.IOException; + +import com.alibaba.qwen.code.cli.transport.TransportOptions; + +import org.junit.jupiter.api.Test; + +class ProcessTransportTest { + + @Test + void shouldStartAndCloseSuccessfully() throws IOException { + TransportOptions transportOptions = new TransportOptions(); + ProcessTransport processTransport = new ProcessTransport(transportOptions); + processTransport.close(); + } + +} From 422998d7f080fdcb2045bbed80290196e11c6d8d Mon Sep 17 00:00:00 2001 From: skyfire Date: Wed, 24 Dec 2025 21:20:47 +0800 Subject: [PATCH 19/65] add ProcessTransport unitTest and fix bug --- .../transport/process/TransportOptionsAdapter.java | 5 ----- .../cli/transport/process/ProcessTransportTest.java | 11 +++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java index e22f2fd27..66113e8cd 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java @@ -39,11 +39,6 @@ class TransportOptionsAdapter { args.add(transportOptions.getModel()); } - if (StringUtils.isNotBlank(transportOptions.getCwd())) { - args.add("--cwd"); - args.add(transportOptions.getCwd()); - } - if (transportOptions.getPermissionMode() != null) { args.add("--permission-mode"); args.add(transportOptions.getPermissionMode().getValue()); diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java index af2a8d363..bdedf3047 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java @@ -1,6 +1,8 @@ package com.alibaba.qwen.code.cli.transport.process; import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; import com.alibaba.qwen.code.cli.transport.TransportOptions; @@ -15,4 +17,13 @@ class ProcessTransportTest { processTransport.close(); } + @Test + void shouldInputWaitForOneLineSuccessfully() throws IOException, ExecutionException, InterruptedException, TimeoutException { + TransportOptions transportOptions = new TransportOptions(); + ProcessTransport processTransport = new ProcessTransport(transportOptions); + + String message = "{\"type\": \"control_request\", \"request_id\": \"1\", \"request\": {\"subtype\": \"initialize\"} }"; + System.out.println(processTransport.inputWaitForOneLine(message)); + } + } From fe7ff5b148b26ba4044bc1a7d8dd5e9951767c41 Mon Sep 17 00:00:00 2001 From: LaZzyMan Date: Fri, 26 Dec 2025 17:09:16 +0800 Subject: [PATCH 20/65] feat: stable-acp-flag --- .gitignore | 1 + docs/users/configuration/settings.md | 2 +- docs/users/integration-zed.md | 2 +- integration-tests/acp-integration.test.ts | 105 +++++++++++++++++- packages/cli/src/config/config.ts | 26 ++++- packages/cli/src/gemini.test.tsx | 1 + .../src/services/acpConnection.ts | 2 +- 7 files changed, 130 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index fac00d412..705216c80 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ package-lock.json .idea *.iml .cursor +.qoder # OS metadata .DS_Store diff --git a/docs/users/configuration/settings.md b/docs/users/configuration/settings.md index 3e87985b8..9cf704dae 100644 --- a/docs/users/configuration/settings.md +++ b/docs/users/configuration/settings.md @@ -381,7 +381,7 @@ Arguments passed directly when running the CLI can override other configurations | `--telemetry-otlp-protocol` | | Sets the OTLP protocol for telemetry (`grpc` or `http`). | | Defaults to `grpc`. See [telemetry](../../developers/development/telemetry) for more information. | | `--telemetry-log-prompts` | | Enables logging of prompts for telemetry. | | See [telemetry](../../developers/development/telemetry) for more information. | | `--checkpointing` | | Enables [checkpointing](../features/checkpointing). | | | -| `--experimental-acp` | | Enables ACP mode (Agent Control Protocol). Useful for IDE/editor integrations like [Zed](../integration-zed). | | Experimental. | +| `--acp` | | Enables ACP mode (Agent Control Protocol). Useful for IDE/editor integrations like [Zed](../integration-zed). | | Stable. Replaces the deprecated `--experimental-acp` flag. | | `--experimental-skills` | | Enables experimental [Agent Skills](../features/skills) (registers the `skill` tool and loads Skills from `.qwen/skills/` and `~/.qwen/skills/`). | | Experimental. | | `--extensions` | `-e` | Specifies a list of extensions to use for the session. | Extension names | If not provided, all available extensions are used. Use the special term `qwen -e none` to disable all extensions. Example: `qwen -e my-extension -e my-other-extension` | | `--list-extensions` | `-l` | Lists all available extensions and exits. | | | diff --git a/docs/users/integration-zed.md b/docs/users/integration-zed.md index cd4cb2ae4..663e23e80 100644 --- a/docs/users/integration-zed.md +++ b/docs/users/integration-zed.md @@ -32,7 +32,7 @@ "Qwen Code": { "type": "custom", "command": "qwen", - "args": ["--experimental-acp"], + "args": ["--acp"], "env": {} } ``` diff --git a/integration-tests/acp-integration.test.ts b/integration-tests/acp-integration.test.ts index 31e32da76..b89292d87 100644 --- a/integration-tests/acp-integration.test.ts +++ b/integration-tests/acp-integration.test.ts @@ -80,10 +80,11 @@ type PermissionHandler = ( /** * Sets up an ACP test environment with all necessary utilities. + * @param useNewFlag - If true, uses --acp; if false, uses --experimental-acp (for backward compatibility testing) */ function setupAcpTest( rig: TestRig, - options?: { permissionHandler?: PermissionHandler }, + options?: { permissionHandler?: PermissionHandler; useNewFlag?: boolean }, ) { const pending = new Map(); let nextRequestId = 1; @@ -95,9 +96,13 @@ function setupAcpTest( const permissionHandler = options?.permissionHandler ?? (() => ({ optionId: 'proceed_once' })); + // Use --acp by default, but allow testing with --experimental-acp for backward compatibility + const acpFlag = + options?.useNewFlag !== false ? '--acp' : '--experimental-acp'; + const agent = spawn( 'node', - [rig.bundlePath, '--experimental-acp', '--no-chat-recording'], + [rig.bundlePath, acpFlag, '--no-chat-recording'], { cwd: rig.testDir!, stdio: ['pipe', 'pipe', 'pipe'], @@ -621,3 +626,99 @@ function setupAcpTest( } }); }); + +(IS_SANDBOX ? describe.skip : describe)( + 'acp flag backward compatibility', + () => { + it('should work with deprecated --experimental-acp flag and show warning', async () => { + const rig = new TestRig(); + rig.setup('acp backward compatibility'); + + const { sendRequest, cleanup, stderr } = setupAcpTest(rig, { + useNewFlag: false, + }); + + try { + const initResult = await sendRequest('initialize', { + protocolVersion: 1, + clientCapabilities: { + fs: { readTextFile: true, writeTextFile: true }, + }, + }); + expect(initResult).toBeDefined(); + + // Verify deprecation warning is shown + const stderrOutput = stderr.join(''); + expect(stderrOutput).toContain('--experimental-acp is deprecated'); + expect(stderrOutput).toContain('Please use --acp instead'); + + await sendRequest('authenticate', { methodId: 'openai' }); + + const newSession = (await sendRequest('session/new', { + cwd: rig.testDir!, + mcpServers: [], + })) as { sessionId: string }; + expect(newSession.sessionId).toBeTruthy(); + + // Verify functionality still works + const promptResult = await sendRequest('session/prompt', { + sessionId: newSession.sessionId, + prompt: [{ type: 'text', text: 'Say hello.' }], + }); + expect(promptResult).toBeDefined(); + } catch (e) { + if (stderr.length) { + console.error('Agent stderr:', stderr.join('')); + } + throw e; + } finally { + await cleanup(); + } + }); + + it('should work with new --acp flag without warnings', async () => { + const rig = new TestRig(); + rig.setup('acp new flag'); + + const { sendRequest, cleanup, stderr } = setupAcpTest(rig, { + useNewFlag: true, + }); + + try { + const initResult = await sendRequest('initialize', { + protocolVersion: 1, + clientCapabilities: { + fs: { readTextFile: true, writeTextFile: true }, + }, + }); + expect(initResult).toBeDefined(); + + // Verify no deprecation warning is shown + const stderrOutput = stderr.join(''); + expect(stderrOutput).not.toContain('--experimental-acp is deprecated'); + + await sendRequest('authenticate', { methodId: 'openai' }); + + const newSession = (await sendRequest('session/new', { + cwd: rig.testDir!, + mcpServers: [], + })) as { sessionId: string }; + expect(newSession.sessionId).toBeTruthy(); + + // Verify functionality works + const promptResult = await sendRequest('session/prompt', { + sessionId: newSession.sessionId, + prompt: [{ type: 'text', text: 'Say hello.' }], + }); + expect(promptResult).toBeDefined(); + } catch (e) { + if (stderr.length) { + console.error('Agent stderr:', stderr.join('')); + } + throw e; + } finally { + await cleanup(); + } + }); + }, +); diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 7cd7d685a..e45ec2a3b 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -111,6 +111,7 @@ export interface CliArgs { telemetryOutfile: string | undefined; allowedMcpServerNames: string[] | undefined; allowedTools: string[] | undefined; + acp: boolean | undefined; experimentalAcp: boolean | undefined; experimentalSkills: boolean | undefined; extensions: string[] | undefined; @@ -304,10 +305,16 @@ export async function parseArguments(settings: Settings): Promise { description: 'Enables checkpointing of file edits', default: false, }) - .option('experimental-acp', { + .option('acp', { type: 'boolean', description: 'Starts the agent in ACP mode', }) + .option('experimental-acp', { + type: 'boolean', + description: + 'Starts the agent in ACP mode (deprecated, use --acp instead)', + hidden: true, + }) .option('experimental-skills', { type: 'boolean', description: 'Enable experimental Skills feature', @@ -589,8 +596,19 @@ export async function parseArguments(settings: Settings): Promise { // The import format is now only controlled by settings.memoryImportFormat // We no longer accept it as a CLI argument - // Apply ACP fallback: if experimental-acp is present but no explicit --channel, treat as ACP - if (result['experimentalAcp'] && !result['channel']) { + // Handle deprecated --experimental-acp flag + if (result['experimentalAcp']) { + console.warn( + '\x1b[33m⚠ Warning: --experimental-acp is deprecated and will be removed in a future release. Please use --acp instead.\x1b[0m', + ); + // Map experimental-acp to acp if acp is not explicitly set + if (!result['acp']) { + (result as Record)['acp'] = true; + } + } + + // Apply ACP fallback: if acp or experimental-acp is present but no explicit --channel, treat as ACP + if ((result['acp'] || result['experimentalAcp']) && !result['channel']) { (result as Record)['channel'] = 'ACP'; } @@ -981,7 +999,7 @@ export async function loadCliConfig( sessionTokenLimit: settings.model?.sessionTokenLimit ?? -1, maxSessionTurns: argv.maxSessionTurns ?? settings.model?.maxSessionTurns ?? -1, - experimentalZedIntegration: argv.experimentalAcp || false, + experimentalZedIntegration: argv.acp || argv.experimentalAcp || false, experimentalSkills: argv.experimentalSkills || false, listExtensions: argv.listExtensions || false, extensions: allExtensions, diff --git a/packages/cli/src/gemini.test.tsx b/packages/cli/src/gemini.test.tsx index 9fa0b8261..064b67fc5 100644 --- a/packages/cli/src/gemini.test.tsx +++ b/packages/cli/src/gemini.test.tsx @@ -460,6 +460,7 @@ describe('gemini.tsx main function kitty protocol', () => { telemetryOutfile: undefined, allowedMcpServerNames: undefined, allowedTools: undefined, + acp: undefined, experimentalAcp: undefined, experimentalSkills: undefined, extensions: undefined, diff --git a/packages/vscode-ide-companion/src/services/acpConnection.ts b/packages/vscode-ide-companion/src/services/acpConnection.ts index 4b2c4028b..219f7809f 100644 --- a/packages/vscode-ide-companion/src/services/acpConnection.ts +++ b/packages/vscode-ide-companion/src/services/acpConnection.ts @@ -95,7 +95,7 @@ export class AcpConnection { const spawnCommand: string = process.execPath; const spawnArgs: string[] = [ cliEntryPath, - '--experimental-acp', + '--acp', '--channel=VSCode', ...extraArgs, ]; From f610133660f2ff6d2c5006bf30bbe2108eca1d29 Mon Sep 17 00:00:00 2001 From: cris Date: Sun, 28 Dec 2025 22:14:16 +0800 Subject: [PATCH 21/65] improve ad hoc method for windows background terminal task --- .../src/services/shellExecutionService.ts | 2 +- packages/core/src/tools/shell.ts | 89 ++++++++----------- 2 files changed, 37 insertions(+), 54 deletions(-) diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index 853a4c89f..c870b5f4e 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -229,7 +229,7 @@ export class ShellExecutionService { stdio: ['ignore', 'pipe', 'pipe'], windowsVerbatimArguments: true, shell: isWindows ? true : 'bash', - detached: !isWindows, + detached: true, env: { ...process.env, QWEN_CODE: '1', diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index c223d0e5f..b4cbb195b 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -30,7 +30,6 @@ import { summarizeToolOutput } from '../utils/summarizer.js'; import type { ShellExecutionConfig, ShellOutputEvent, - ShellExecutionResult, } from '../services/shellExecutionService.js'; import { ShellExecutionService } from '../services/shellExecutionService.js'; import { formatMemoryUsage } from '../utils/formatters.js'; @@ -159,12 +158,7 @@ export class ShellToolInvocation extends BaseToolInvocation< // On Windows, we rely on the race logic below to handle background tasks. // We just ensure the command string is clean. if (isWindows && shouldRunInBackground) { - let cmd = finalCommand.trim(); - // Remove trailing & (common Linux habit, invalid on Windows at end of line) - while (cmd.endsWith('&')) { - cmd = cmd.slice(0, -1).trim(); - } - finalCommand = cmd; + finalCommand = finalCommand.trim().replace(/&+$/, '').trim(); } // pgrep is not available on Windows, so we can't get background PIDs @@ -234,60 +228,49 @@ export class ShellToolInvocation extends BaseToolInvocation< setPidCallback(pid); } - let result: ShellExecutionResult; - if (shouldRunInBackground && isWindows) { - // For Windows background tasks, we wait a short time to catch immediate errors. - // If it's still running, we return early. - const startupDelay = 1000; - const raceResult = await Promise.race([ - resultPromise, - new Promise((resolve) => - setTimeout(() => resolve(null), startupDelay), - ), - ]); + if (shouldRunInBackground) { + // Check for obvious startup errors from captured output + const outputStr = + typeof cumulativeOutput === 'string' + ? cumulativeOutput + : JSON.stringify(cumulativeOutput); - if (raceResult === null) { - // Timeout reached, process is still running. - // throw new Error(`DEBUG: raceResult is null. Output: ${JSON.stringify(cumulativeOutput)}`); + const errorPatterns = [ + 'is not recognized as an internal or external command', + 'The system cannot find the path specified', + 'Access is denied', + 'command not found', + 'No such file or directory', + 'Permission denied', + ]; - // Check for common Windows error messages in the output - const outputStr = - typeof cumulativeOutput === 'string' - ? cumulativeOutput - : JSON.stringify(cumulativeOutput); - console.log('DEBUG: outputStr:', outputStr); - const errorPatterns = [ - 'is not recognized as an internal or external command', - 'The system cannot find the path specified', - 'Access is denied', - ]; + const hasEarlyError = errorPatterns.some((pat) => + outputStr.includes(pat), + ); - if (errorPatterns.some((pattern) => outputStr.includes(pattern))) { - return { - llmContent: `Command failed to start: ${outputStr}`, - returnDisplay: `Command failed to start: ${outputStr}`, - error: { - type: ToolErrorType.EXECUTION_FAILED, - message: `Command failed to start: ${outputStr}`, - }, - }; - } - - const pidMsg = pid ? ` PID: ${pid}` : ''; - const winHint = isWindows - ? ' (Note: Use taskkill /F /T /PID to stop)' - : ''; + if (hasEarlyError) { return { - llmContent: `Background command started.${pidMsg}${winHint}`, - returnDisplay: `Background command started.${pidMsg}${winHint}`, + llmContent: `Command failed to start: ${outputStr}`, + returnDisplay: `Command failed to start: ${outputStr}`, + error: { + type: ToolErrorType.EXECUTION_FAILED, + message: `Command failed to start: ${outputStr}`, + }, }; - } else { - result = raceResult; } - } else { - result = await resultPromise; + + const pidMsg = pid ? ` PID: ${pid}` : ''; + const winHint = isWindows + ? ' (Note: Use taskkill /F /T /PID to stop)' + : ''; + return { + llmContent: `Background command started.${pidMsg}${winHint}`, + returnDisplay: `Background command started.${pidMsg}${winHint}`, + }; } + const result = await resultPromise; + const backgroundPIDs: number[] = []; if (os.platform() !== 'win32') { if (fs.existsSync(tempFilePath)) { From 98c043bf50a969d8d4b4d46fff4878149a9c9a0e Mon Sep 17 00:00:00 2001 From: xuewenjie Date: Mon, 29 Dec 2025 11:37:54 +0800 Subject: [PATCH 22/65] test: update tests for detached process changes --- .../services/shellExecutionService.test.ts | 2 +- packages/core/src/tools/shell.test.ts | 27 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/packages/core/src/services/shellExecutionService.test.ts b/packages/core/src/services/shellExecutionService.test.ts index c5a6e0776..e63fba28d 100644 --- a/packages/core/src/services/shellExecutionService.test.ts +++ b/packages/core/src/services/shellExecutionService.test.ts @@ -829,7 +829,7 @@ describe('ShellExecutionService child_process fallback', () => { [], expect.objectContaining({ shell: true, - detached: false, + detached: true, }), ); }); diff --git a/packages/core/src/tools/shell.test.ts b/packages/core/src/tools/shell.test.ts index eb8a17418..8db33b563 100644 --- a/packages/core/src/tools/shell.test.ts +++ b/packages/core/src/tools/shell.test.ts @@ -960,32 +960,5 @@ spanning multiple lines"`; {}, ); }); - - it('should detect immediate failure in Windows background task', async () => { - vi.mocked(os.platform).mockReturnValue('win32'); - const mockAbortSignal = new AbortController().signal; - - const invocation = shellTool.build({ - command: 'invalid_command', - is_background: true, - }); - - const promise = invocation.execute(mockAbortSignal); - - // Wait a tick to ensure mockShellOutputCallback is assigned - await new Promise((resolve) => setTimeout(resolve, 0)); - - if (mockShellOutputCallback) { - mockShellOutputCallback({ - type: 'data', - chunk: - "'invalid_command' is not recognized as an internal or external command,\r\noperable program or batch file.\r\n", - }); - } - - const result = await promise; - expect(result.error).toBeDefined(); - expect(result.llmContent).toContain('Command failed to start'); - }); }); }); From 61aad5a16253167d05d96ec42e18d420766614a7 Mon Sep 17 00:00:00 2001 From: LaZzyMan Date: Mon, 29 Dec 2025 16:59:09 +0800 Subject: [PATCH 23/65] fix: missing whitespaces for stream-json/json output format via GLM 4.7 model --- .../io/BaseJsonOutputAdapter.test.ts | 61 ++++++++++++++++++ .../io/BaseJsonOutputAdapter.ts | 15 ++++- .../io/StreamJsonOutputAdapter.test.ts | 62 +++++++++++++++++++ 3 files changed, 135 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/nonInteractive/io/BaseJsonOutputAdapter.test.ts b/packages/cli/src/nonInteractive/io/BaseJsonOutputAdapter.test.ts index 0ba94cbb2..be04b7f2b 100644 --- a/packages/cli/src/nonInteractive/io/BaseJsonOutputAdapter.test.ts +++ b/packages/cli/src/nonInteractive/io/BaseJsonOutputAdapter.test.ts @@ -630,6 +630,67 @@ describe('BaseJsonOutputAdapter', () => { expect(state.blocks).toHaveLength(0); }); + + it('should preserve whitespace in thinking content', () => { + const state = adapter.exposeCreateMessageState(); + adapter.startAssistantMessage(); + + adapter.exposeAppendThinking( + state, + '', + 'The user just said "Hello"', + null, + ); + + expect(state.blocks).toHaveLength(1); + expect(state.blocks[0]).toMatchObject({ + type: 'thinking', + thinking: 'The user just said "Hello"', + }); + // Verify spaces are preserved + const block = state.blocks[0] as { thinking: string }; + expect(block.thinking).toContain('user just'); + expect(block.thinking).not.toContain('userjust'); + }); + + it('should preserve whitespace when appending multiple thinking fragments', () => { + const state = adapter.exposeCreateMessageState(); + adapter.startAssistantMessage(); + + // Simulate streaming thinking content in fragments + adapter.exposeAppendThinking(state, '', 'The user just', null); + adapter.exposeAppendThinking(state, '', ' said "Hello"', null); + adapter.exposeAppendThinking( + state, + '', + '. This is a simple greeting', + null, + ); + + expect(state.blocks).toHaveLength(1); + const block = state.blocks[0] as { thinking: string }; + // Verify the complete text with all spaces preserved + expect(block.thinking).toBe( + 'The user just said "Hello". This is a simple greeting', + ); + // Verify specific space preservation + expect(block.thinking).toContain('user just '); + expect(block.thinking).toContain(' said'); + expect(block.thinking).toContain('". This'); + expect(block.thinking).not.toContain('userjust'); + expect(block.thinking).not.toContain('justsaid'); + }); + + it('should preserve leading and trailing whitespace in description', () => { + const state = adapter.exposeCreateMessageState(); + adapter.startAssistantMessage(); + + adapter.exposeAppendThinking(state, '', ' content with spaces ', null); + + expect(state.blocks).toHaveLength(1); + const block = state.blocks[0] as { thinking: string }; + expect(block.thinking).toBe(' content with spaces '); + }); }); describe('appendToolUse', () => { diff --git a/packages/cli/src/nonInteractive/io/BaseJsonOutputAdapter.ts b/packages/cli/src/nonInteractive/io/BaseJsonOutputAdapter.ts index ef6655370..072497000 100644 --- a/packages/cli/src/nonInteractive/io/BaseJsonOutputAdapter.ts +++ b/packages/cli/src/nonInteractive/io/BaseJsonOutputAdapter.ts @@ -816,9 +816,18 @@ export abstract class BaseJsonOutputAdapter { parentToolUseId?: string | null, ): void { const actualParentToolUseId = parentToolUseId ?? null; - const fragment = [subject?.trim(), description?.trim()] - .filter((value) => value && value.length > 0) - .join(': '); + + // Build fragment without trimming to preserve whitespace in streaming content + // Only filter out null/undefined/empty values + const parts: string[] = []; + if (subject && subject.length > 0) { + parts.push(subject); + } + if (description && description.length > 0) { + parts.push(description); + } + + const fragment = parts.join(': '); if (!fragment) { return; } diff --git a/packages/cli/src/nonInteractive/io/StreamJsonOutputAdapter.test.ts b/packages/cli/src/nonInteractive/io/StreamJsonOutputAdapter.test.ts index d0bd23255..ff3aa1f5d 100644 --- a/packages/cli/src/nonInteractive/io/StreamJsonOutputAdapter.test.ts +++ b/packages/cli/src/nonInteractive/io/StreamJsonOutputAdapter.test.ts @@ -323,6 +323,68 @@ describe('StreamJsonOutputAdapter', () => { }); }); + it('should preserve whitespace in thinking content (issue #1356)', () => { + adapter.processEvent({ + type: GeminiEventType.Thought, + value: { + subject: '', + description: 'The user just said "Hello"', + }, + }); + + const message = adapter.finalizeAssistantMessage(); + expect(message.message.content).toHaveLength(1); + const block = message.message.content[0] as { + type: string; + thinking: string; + }; + expect(block.type).toBe('thinking'); + expect(block.thinking).toBe('The user just said "Hello"'); + // Verify spaces are preserved + expect(block.thinking).toContain('user just'); + expect(block.thinking).not.toContain('userjust'); + }); + + it('should preserve whitespace when streaming multiple thinking fragments (issue #1356)', () => { + // Simulate streaming thinking content in multiple events + adapter.processEvent({ + type: GeminiEventType.Thought, + value: { + subject: '', + description: 'The user just', + }, + }); + adapter.processEvent({ + type: GeminiEventType.Thought, + value: { + subject: '', + description: ' said "Hello"', + }, + }); + adapter.processEvent({ + type: GeminiEventType.Thought, + value: { + subject: '', + description: '. This is a simple greeting', + }, + }); + + const message = adapter.finalizeAssistantMessage(); + expect(message.message.content).toHaveLength(1); + const block = message.message.content[0] as { + type: string; + thinking: string; + }; + expect(block.thinking).toBe( + 'The user just said "Hello". This is a simple greeting', + ); + // Verify specific spaces are preserved + expect(block.thinking).toContain('user just '); + expect(block.thinking).toContain(' said'); + expect(block.thinking).not.toContain('userjust'); + expect(block.thinking).not.toContain('justsaid'); + }); + it('should append tool use from ToolCallRequest events', () => { adapter.processEvent({ type: GeminiEventType.ToolCallRequest, From 4154493640559ffbcc052ba5ef65347c3ebf0f2c Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 29 Dec 2025 21:44:02 +0800 Subject: [PATCH 24/65] message and session use --- packages/sdk-java/pom.xml | 6 + .../com/alibaba/qwen/code/cli/Options.java | 6 + .../com/alibaba/qwen/code/cli/QwenCli.java | 54 ++ .../protocol/data/CLIPermissionDenial.java | 38 ++ .../code/cli/protocol/data/Capabilities.java | 60 ++ .../code/cli/protocol/data/ExtendedUsage.java | 67 ++ .../cli/protocol/data/InitializeConfig.java | 40 ++ .../code/cli/protocol/data/ModelUsage.java | 58 ++ .../qwen/code/cli/protocol/data/Usage.java | 56 ++ .../code/cli/protocol/message/Message.java | 5 + .../cli/protocol/message/MessageBase.java | 22 + .../protocol/message/SDKResultMessage.java | 151 +++++ .../protocol/message/SDKSystemMessage.java | 213 +++++++ .../cli/protocol/message/SDKUserMessage.java | 90 +++ .../assistant/APIAssistantMessage.java | 76 +++ .../assistant/SDKAssistantMessage.java | 49 ++ .../message/assistant/block/Annotation.java | 28 + .../message/assistant/block/ContentBlock.java | 32 + .../message/assistant/block/TextBlock.java | 16 + .../assistant/block/ThinkingBlock.java | 25 + .../assistant/block/ToolResultBlock.java | 40 ++ .../message/assistant/block/ToolUseBlock.java | 49 ++ .../control/CLIControlInitializeRequest.java | 28 + .../control/CLIControlInitializeResponse.java | 24 + .../message/control/CLIControlRequest.java | 44 ++ .../message/control/CLIControlResponse.java | 54 ++ .../qwen/code/cli/protocol/protocol.ts | 594 ++++++++++++++++++ .../qwen/code/cli/session/Session.java | 89 +++ .../session/event/SessionEventConsumers.java | 15 + .../event/SessionEventSimpleConsumers.java | 23 + .../exception/SessionCloseException.java | 22 + .../exception/SessionSendPromptException.java | 22 + .../exception/SessionStartException.java | 22 + .../qwen/code/cli/transport/Transport.java | 18 + .../transport/process/ProcessTransport.java | 16 +- .../process/TransportOptionsAdapter.java | 4 +- .../alibaba/qwen/code/cli/QwenCliTest.java | 23 + .../qwen/code/cli/session/SessionTest.java | 58 ++ .../process/ProcessTransportTest.java | 65 +- 39 files changed, 2296 insertions(+), 6 deletions(-) create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/protocol.ts create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java create mode 100644 packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java create mode 100644 packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 1d1c7c25a..45c9ea895 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -27,6 +27,7 @@ 3.6.0 5.14.1 1.3.16 + 2.0.60 @@ -51,6 +52,11 @@ commons-lang3 3.20.0 + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + org.junit.jupiter junit-jupiter diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java new file mode 100644 index 000000000..82b0a4652 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java @@ -0,0 +1,6 @@ +package com.alibaba.qwen.code.cli; + +import com.alibaba.qwen.code.cli.transport.TransportOptions; + +public class Options extends TransportOptions { +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java new file mode 100644 index 000000000..065ff9e73 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java @@ -0,0 +1,54 @@ +package com.alibaba.qwen.code.cli; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.qwen.code.cli.protocol.message.Message; +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.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.process.ProcessTransport; + +public class QwenCli { + public static List query(String prompt) { + Transport transport; + try { + transport = new ProcessTransport(); + } 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); + } + + final List response = new ArrayList<>(); + try { + session.sendPrompt(prompt, new SessionEventSimpleConsumers() { + @Override + public void onSystemMessage(SDKSystemMessage systemMessage) { + response.add(systemMessage); + } + + @Override + public void onAssistantMessage(SDKAssistantMessage assistantMessage) { + response.add(assistantMessage); + } + }); + } catch (Exception e) { + throw new RuntimeException("sendPrompt error!", e); + } + + try { + session.close(); + } catch (Exception e) { + throw new RuntimeException("close Session error!", e); + } + return response; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java new file mode 100644 index 000000000..4abd68bc3 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java new file mode 100644 index 000000000..13200b654 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java new file mode 100644 index 000000000..4965f4b8c --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java @@ -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; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java new file mode 100644 index 000000000..ccafed4f0 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java new file mode 100644 index 000000000..22787f232 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java new file mode 100644 index 000000000..1222b16f2 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java new file mode 100644 index 000000000..de43924df --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java @@ -0,0 +1,5 @@ +package com.alibaba.qwen.code.cli.protocol.message; + +public interface Message { + String getType(); +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java new file mode 100644 index 000000000..c66df12c4 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java new file mode 100644 index 000000000..dfa2275ff --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java @@ -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 modelUsage; + + @JSONField(name = "permission_denials") + private List 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 getModelUsage() { + return modelUsage; + } + + public void setModelUsage(Map modelUsage) { + this.modelUsage = modelUsage; + } + + public List getPermissionDenials() { + return permissionDenials; + } + + public void setPermissionDenials(List 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; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java new file mode 100644 index 000000000..22870cb85 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java @@ -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 tools; + @JSONField(name = "mcp_servers") + private List mcpServers; + private String model; + @JSONField(name = "permission_mode") + private String permissionMode; + @JSONField(name = "slash_commands") + private List slashCommands; + @JSONField(name = "qwen_code_version") + private String qwenCodeVersion; + @JSONField(name = "output_style") + private String outputStyle; + private List agents; + private List skills; + private Map 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 getTools() { + return tools; + } + + public void setTools(List tools) { + this.tools = tools; + } + + public List getMcpServers() { + return mcpServers; + } + + public void setMcpServers(List 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 getSlashCommands() { + return slashCommands; + } + + public void setSlashCommands(List 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 getAgents() { + return agents; + } + + public void setAgents(List agents) { + this.agents = agents; + } + + public List getSkills() { + return skills; + } + + public void setSkills(List skills) { + this.skills = skills; + } + + public Map getCapabilities() { + return capabilities; + } + + public void setCapabilities(Map 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; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java new file mode 100644 index 000000000..e896b08c4 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java @@ -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 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 getOptions() { + return options; + } + + public SDKUserMessage setOptions(Map 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; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java new file mode 100644 index 000000000..5a0b3776c --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java @@ -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 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 getContent() { + return content; + } + + public void setContent(List content) { + this.content = content; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java new file mode 100644 index 000000000..7e906fc44 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java @@ -0,0 +1,49 @@ +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 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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java new file mode 100644 index 000000000..5e8b9a2b5 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java new file mode 100644 index 000000000..3e72ad7d0 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java @@ -0,0 +1,32 @@ +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; + +@JSONType(typeKey = "type", typeName = "ContentBlock", seeAlso = { TextBlock.class, ToolResultBlock.class, ThinkingBlock.class, ToolUseBlock.class }) +public class ContentBlock { + protected String type; + protected List annotations; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getAnnotations() { + return annotations; + } + + public void setAnnotations(List annotations) { + this.annotations = annotations; + } + + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java new file mode 100644 index 000000000..86e5513d3 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java @@ -0,0 +1,16 @@ +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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java new file mode 100644 index 000000000..fa479563f --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java @@ -0,0 +1,25 @@ +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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java new file mode 100644 index 000000000..3d7acfea2 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java @@ -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 + + @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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java new file mode 100644 index 000000000..58a3bd4fc --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java @@ -0,0 +1,49 @@ +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 input; + private List 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 getInput() { + return input; + } + + public void setInput(Map input) { + this.input = input; + } + + public List getAnnotations() { + return annotations; + } + + public void setAnnotations(List annotations) { + this.annotations = annotations; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java new file mode 100644 index 000000000..3d217289c --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java new file mode 100644 index 000000000..284781a76 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java new file mode 100644 index 000000000..e6ea7b956 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java @@ -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 extends MessageBase { + @JSONField(name = "request_id") + private String requestId = UUID.randomUUID().toString(); + + private R request; + + public CLIControlRequest() { + super(); + type = "control_request"; + } + + public static CLIControlRequest create(T request) { + CLIControlRequest controlRequest = new CLIControlRequest<>(); + controlRequest.setRequest(request); + return controlRequest; + } + + public String getRequestId() { + return requestId; + } + + public CLIControlRequest setRequestId(String requestId) { + this.requestId = requestId; + return this; + } + + public R getRequest() { + return request; + } + + public CLIControlRequest setRequest(R request) { + this.request = request; + return this; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java new file mode 100644 index 000000000..5e193e2cd --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java @@ -0,0 +1,54 @@ +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 extends MessageBase { + private Response response; + + public CLIControlResponse() { + super(); + this.type = "control_response"; + } + + public Response getResponse() { + return response; + } + + public void setResponse(Response response) { + this.response = response; + } + + public static class Response { + @JSONField(name = "request_id") + private String requestId; + private String subtype; + R response; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } + + public R getResponse() { + return response; + } + + public void setResponse(R response) { + this.response = response; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/protocol.ts b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/protocol.ts new file mode 100644 index 000000000..e5eeb1212 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/protocol.ts @@ -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; +} + +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; + 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; + 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; + 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; + cwd?: string; + url?: string; + httpUrl?: string; + headers?: Record; + tcp?: string; + timeout?: number; + trust?: boolean; + description?: string; + includeTools?: string[]; + excludeTools?: string[]; + extensionName?: string; + oauth?: Record; + 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; + +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; + /** + * External MCP servers that should be managed by the CLI. + */ + mcpServers?: Record; + 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; + 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; + runConfig?: Partial; + 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', +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java new file mode 100644 index 000000000..346b0f1f2 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java @@ -0,0 +1,89 @@ +package com.alibaba.qwen.code.cli.session; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import com.alibaba.qwen.code.cli.protocol.data.Capabilities; +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.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.session.event.SessionEventConsumers; +import com.alibaba.qwen.code.cli.session.exception.SessionCloseException; +import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException; +import com.alibaba.qwen.code.cli.session.exception.SessionStartException; +import com.alibaba.qwen.code.cli.transport.Transport; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Session { + private final Transport transport; + private Capabilities capabilities; + private static final Logger log = LoggerFactory.getLogger(Session.class); + + public Session(Transport transport) throws SessionStartException { + if (transport == null || !transport.isAvailable()) { + throw new SessionStartException("Transport is not available"); + } + this.transport = transport; + start(); + } + + private void start() throws SessionStartException { + try { + String response = transport.inputWaitForOneLine(CLIControlRequest.create(new CLIControlInitializeRequest()).toString()); + CLIControlResponse cliControlResponse = JSON.parseObject(response, new TypeReference>() {}); + this.capabilities = cliControlResponse.getResponse().getResponse().getCapabilities(); + } catch (Exception e) { + throw new SessionStartException("Failed to initialize the session", e); + } + } + + public void close() throws SessionCloseException { + try { + transport.close(); + } catch (Exception e) { + throw new SessionCloseException("Failed to close the session", e); + } + } + + public Capabilities getCapabilities() { + return capabilities; + } + + public void sendPrompt(String prompt, SessionEventConsumers sessionEventConsumers) throws SessionSendPromptException { + if (!transport.isAvailable()) { + throw new SessionSendPromptException("Session is not available"); + } + + try { + transport.inputWaitForMultiLine(new SDKUserMessage().setContent(prompt).toString(), (line) -> { + log.debug("read a message from agent {}", line); + JSONObject jsonObject = JSON.parseObject(line); + + String messageType = jsonObject.getString("type"); + if ("system".equals(messageType)) { + sessionEventConsumers.onSystemMessage(JSON.parseObject(line, SDKSystemMessage.class)); + return false; + } else if ("assistant".equals(messageType)) { + sessionEventConsumers.onAssistantMessage(JSON.parseObject(line, SDKAssistantMessage.class)); + return false; + } else if ("result".equals(messageType)) { + sessionEventConsumers.onResultMessage(JSON.parseObject(line, SDKResultMessage.class)); + return true; + } else { + log.warn("unknown message type: {}", messageType); + sessionEventConsumers.onOtherMessage(line); + return false; + } + }); + } catch (Exception e) { + throw new SessionSendPromptException("Failed to send prompt", e); + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java new file mode 100644 index 000000000..9f9bb6fc1 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java @@ -0,0 +1,15 @@ +package com.alibaba.qwen.code.cli.session.event; + +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; + +public interface SessionEventConsumers { + void onSystemMessage(SDKSystemMessage systemMessage); + + void onResultMessage(SDKResultMessage resultMessage); + + void onAssistantMessage(SDKAssistantMessage assistantMessage); + + void onOtherMessage(String message); +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java new file mode 100644 index 000000000..584354d43 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java @@ -0,0 +1,23 @@ +package com.alibaba.qwen.code.cli.session.event; + +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; + +public class SessionEventSimpleConsumers implements SessionEventConsumers { + @Override + public void onSystemMessage(SDKSystemMessage systemMessage) { + } + + @Override + public void onResultMessage(SDKResultMessage resultMessage) { + } + + @Override + public void onAssistantMessage(SDKAssistantMessage assistantMessage) { + } + + @Override + public void onOtherMessage(String message) { + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java new file mode 100644 index 000000000..3db39e793 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java @@ -0,0 +1,22 @@ +package com.alibaba.qwen.code.cli.session.exception; + +public class SessionCloseException extends Exception { + public SessionCloseException() { + } + + public SessionCloseException(String message) { + super(message); + } + + public SessionCloseException(String message, Throwable cause) { + super(message, cause); + } + + public SessionCloseException(Throwable cause) { + super(cause); + } + + public SessionCloseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java new file mode 100644 index 000000000..74de3bba7 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java @@ -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); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java new file mode 100644 index 000000000..9d30f2367 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java @@ -0,0 +1,22 @@ +package com.alibaba.qwen.code.cli.session.exception; + +public class SessionStartException extends Exception { + public SessionStartException() { + } + + public SessionStartException(String message) { + super(message); + } + + public SessionStartException(String message, Throwable cause) { + super(message, cause); + } + + public SessionStartException(Throwable cause) { + super(cause); + } + + public SessionStartException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java new file mode 100644 index 000000000..5b66cbc90 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java @@ -0,0 +1,18 @@ +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 { + void close() throws IOException; + + boolean isAvailable(); + + String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException; + + void inputWaitForMultiLine(String message, Function callBackFunction) throws IOException; + + void inputNoWaitResponse(String message) throws IOException; +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java index 3adc1072b..b7d62b5d7 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java @@ -1,5 +1,6 @@ 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 org.apache.commons.lang3.exception.ContextedRuntimeException; @@ -19,7 +20,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Function; -public class ProcessTransport { +public class ProcessTransport implements Transport { private static final Logger log = LoggerFactory.getLogger(ProcessTransport.class); TransportOptionsAdapter transportOptionsAdapter; @@ -31,6 +32,10 @@ public class ProcessTransport { protected BufferedReader processOutput; protected BufferedReader processError; + public ProcessTransport() throws IOException { + this(new TransportOptions()); + } + public ProcessTransport(TransportOptions transportOptions) throws IOException { this.transportOptionsAdapter = new TransportOptionsAdapter(transportOptions); turnTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeoutMs(); @@ -56,6 +61,7 @@ public class ProcessTransport { startErrorReading(); } + @Override public void close() throws IOException { if (processInput != null) { processInput.close(); @@ -71,6 +77,12 @@ public class ProcessTransport { } } + @Override + public boolean isAvailable() { + return process != null && process.isAlive(); + } + + @Override public String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException { return inputWaitForOneLine(message, turnTimeoutMs); } @@ -106,6 +118,7 @@ public class ProcessTransport { } } + @Override public void inputWaitForMultiLine(String message, Function callBackFunction) throws IOException { inputWaitForMultiLine(message, callBackFunction, turnTimeoutMs); } @@ -132,6 +145,7 @@ public class ProcessTransport { } } + @Override public void inputNoWaitResponse(String message) throws IOException { log.debug("input message to agent: {}", message); processInput.write(message); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java index 66113e8cd..4c6b7d48d 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java @@ -76,7 +76,9 @@ class TransportOptionsAdapter { } private TransportOptions addDefaultTransportOptions(TransportOptions userTransportOptions) { - TransportOptions transportOptions = userTransportOptions.clone(); + TransportOptions transportOptions = Optional.ofNullable(userTransportOptions) + .map(TransportOptions::clone) + .orElse(new TransportOptions()); if (StringUtils.isBlank(transportOptions.getPathToQwenExecutable())) { transportOptions.setPathToQwenExecutable("qwen"); diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java new file mode 100644 index 000000000..01295ce6c --- /dev/null +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java @@ -0,0 +1,23 @@ +package com.alibaba.qwen.code.cli; + +import java.util.List; + +import com.alibaba.qwen.code.cli.protocol.message.Message; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.*; + +class QwenCliTest { + + private static final Logger log = LoggerFactory.getLogger(QwenCliTest.class); + @Test + void query() { + List result = QwenCli.query("hello world"); + log.info("result: {}", result); + assertNotNull(result); + } +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java new file mode 100644 index 000000000..69898b948 --- /dev/null +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java @@ -0,0 +1,58 @@ +package com.alibaba.qwen.code.cli.session; + +import java.io.IOException; + +import com.alibaba.fastjson2.JSON; +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.session.event.SessionEventConsumers; +import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers; +import com.alibaba.qwen.code.cli.session.exception.SessionCloseException; +import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException; +import com.alibaba.qwen.code.cli.session.exception.SessionStartException; +import com.alibaba.qwen.code.cli.transport.Transport; +import com.alibaba.qwen.code.cli.transport.process.ProcessTransport; + +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 sendPrompt() throws IOException, SessionStartException, SessionSendPromptException, SessionCloseException { + Transport transport = new ProcessTransport(); + Session session = new Session(transport); + session.sendPrompt("hello world", new SessionEventSimpleConsumers() { + @Override + public void onSystemMessage(SDKSystemMessage systemMessage) { + log.info("systemMessage: {}", systemMessage); + } + + @Override + public void onResultMessage(SDKResultMessage resultMessage) { + log.info("resultMessage: {}", resultMessage); + } + + @Override + public void onAssistantMessage(SDKAssistantMessage assistantMessage) { + log.info("assistantMessage: {}", assistantMessage); + } + + @Override + public void onOtherMessage(String message) { + log.info("otherMessage: {}", message); + } + }); + session.close(); + } + + @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); + } +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java index bdedf3047..721b203db 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java @@ -1,29 +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(); - ProcessTransport processTransport = new ProcessTransport(transportOptions); - processTransport.close(); + Transport transport = new ProcessTransport(transportOptions); + transport.close(); } @Test void shouldInputWaitForOneLineSuccessfully() throws IOException, ExecutionException, InterruptedException, TimeoutException { TransportOptions transportOptions = new TransportOptions(); - ProcessTransport processTransport = new ProcessTransport(transportOptions); + Transport transport = new ProcessTransport(transportOptions); String message = "{\"type\": \"control_request\", \"request_id\": \"1\", \"request\": {\"subtype\": \"initialize\"} }"; - System.out.println(processTransport.inputWaitForOneLine(message)); + 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 response = JSON.parseObject(responseMsg, + new TypeReference>() {}); + 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"))); } } From 4db50d415805c017fea86e8079256e907478f687 Mon Sep 17 00:00:00 2001 From: cris Date: Tue, 30 Dec 2025 16:00:55 +0800 Subject: [PATCH 25/65] fix resume unwork on windows --- .../cli/src/ui/components/StandaloneSessionPicker.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/ui/components/StandaloneSessionPicker.tsx b/packages/cli/src/ui/components/StandaloneSessionPicker.tsx index bac7f23df..c81531159 100644 --- a/packages/cli/src/ui/components/StandaloneSessionPicker.tsx +++ b/packages/cli/src/ui/components/StandaloneSessionPicker.tsx @@ -87,7 +87,13 @@ export async function showResumeSessionPicker( let selectedId: string | undefined; const { unmount, waitUntilExit } = render( - + { @@ -115,7 +121,6 @@ export async function showResumeSessionPicker( if (process.stdin.isTTY && !wasRaw && !selectedId) { process.stdin.setRawMode(false); } - resolve(selectedId); }); }); From e3c20b03bddf073b63157e284eea4400133b0b78 Mon Sep 17 00:00:00 2001 From: cris Date: Tue, 30 Dec 2025 16:03:11 +0800 Subject: [PATCH 26/65] reslove blank --- packages/cli/src/ui/components/StandaloneSessionPicker.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/ui/components/StandaloneSessionPicker.tsx b/packages/cli/src/ui/components/StandaloneSessionPicker.tsx index c81531159..13a176c62 100644 --- a/packages/cli/src/ui/components/StandaloneSessionPicker.tsx +++ b/packages/cli/src/ui/components/StandaloneSessionPicker.tsx @@ -121,6 +121,7 @@ export async function showResumeSessionPicker( if (process.stdin.isTTY && !wasRaw && !selectedId) { process.stdin.setRawMode(false); } + resolve(selectedId); }); }); From 15912892f245f14a1001387288f04a8e5bcc849a Mon Sep 17 00:00:00 2001 From: LaZzyMan Date: Tue, 30 Dec 2025 19:40:24 +0800 Subject: [PATCH 27/65] fix: missing error throw in non-Interactive mode --- packages/cli/src/nonInteractiveCli.test.ts | 46 ++++++++++++++++++++++ packages/cli/src/nonInteractiveCli.ts | 2 + 2 files changed, 48 insertions(+) diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 07fd168fc..b45d509f9 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -771,6 +771,52 @@ describe('runNonInteractive', () => { ); }); + it('should handle API errors in text mode and exit with error code', async () => { + (mockConfig.getOutputFormat as Mock).mockReturnValue(OutputFormat.TEXT); + setupMetricsMock(); + + // Simulate an API error event (like 401 unauthorized) + const apiErrorEvent: ServerGeminiStreamEvent = { + type: GeminiEventType.Error, + value: { + error: { + message: '401 Incorrect API key provided', + status: 401, + }, + }, + }; + + mockGeminiClient.sendMessageStream.mockReturnValue( + createStreamFromEvents([apiErrorEvent]), + ); + + let thrownError: Error | null = null; + try { + await runNonInteractive( + mockConfig, + mockSettings, + 'Test input', + 'prompt-id-api-error', + ); + // Should not reach here + expect.fail('Expected error to be thrown'); + } catch (error) { + thrownError = error as Error; + } + + // Should throw with the API error message + expect(thrownError).toBeTruthy(); + expect(thrownError?.message).toContain('401'); + expect(thrownError?.message).toContain('Incorrect API key provided'); + + // Verify error was written to stderr + expect(processStderrSpy).toHaveBeenCalled(); + const stderrCalls = processStderrSpy.mock.calls; + const errorOutput = stderrCalls.map((call) => call[0]).join(''); + expect(errorOutput).toContain('401'); + expect(errorOutput).toContain('Incorrect API key provided'); + }); + it('should handle FatalInputError with custom exit code in JSON format', async () => { (mockConfig.getOutputFormat as Mock).mockReturnValue(OutputFormat.JSON); setupMetricsMock(); diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index 067f190b9..3eba267c8 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -308,6 +308,8 @@ export async function runNonInteractive( config.getContentGeneratorConfig()?.authType, ); process.stderr.write(`${errorText}\n`); + // Throw error to exit with non-zero code + throw new Error(errorText); } } } From ac7ba95d65fd3e26c0292b6bf821c63fe2a6ad8d Mon Sep 17 00:00:00 2001 From: skyfire Date: Tue, 30 Dec 2025 20:08:05 +0800 Subject: [PATCH 28/65] add permission --- .../com/alibaba/qwen/code/cli/QwenCli.java | 4 +- .../data}/PermissionMode.java | 2 +- .../cli/protocol/data/behavior/Allow.java | 23 +++ .../cli/protocol/data/behavior/Behavior.java | 25 +++ .../code/cli/protocol/data/behavior/Deny.java | 22 +++ .../control/CLIControlInterruptRequest.java | 13 ++ .../control/CLIControlPermissionRequest.java | 112 +++++++++++ .../control/CLIControlPermissionResponse.java | 28 +++ .../message/control/CLIControlResponse.java | 17 +- .../control/CLIControlSetModelRequest.java | 22 +++ .../CLIControlSetPermissionModeRequest.java | 23 +++ .../qwen/code/cli/session/Session.java | 178 ++++++++++++++++-- .../session/event/SessionEventConsumers.java | 22 ++- .../event/SessionEventSimpleConsumers.java | 32 +++- .../exception/SessionCloseException.java | 22 --- .../exception/SessionControlException.java | 22 +++ .../exception/SessionStartException.java | 22 --- .../qwen/code/cli/transport/Transport.java | 4 + .../code/cli/transport/TransportOptions.java | 11 ++ .../transport/process/ProcessTransport.java | 23 ++- .../process/TransportOptionsAdapter.java | 5 + .../qwen/code/cli/session/SessionTest.java | 101 +++++++++- .../cli/transport/PermissionModeTest.java | 2 + packages/sdk-java/todo | 6 + 24 files changed, 648 insertions(+), 93 deletions(-) rename packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/{transport => protocol/data}/PermissionMode.java (92%) create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java create mode 100644 packages/sdk-java/todo diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java index 065ff9e73..0471ab692 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java @@ -31,12 +31,12 @@ public class QwenCli { try { session.sendPrompt(prompt, new SessionEventSimpleConsumers() { @Override - public void onSystemMessage(SDKSystemMessage systemMessage) { + public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { response.add(systemMessage); } @Override - public void onAssistantMessage(SDKAssistantMessage assistantMessage) { + public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) { response.add(assistantMessage); } }); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/PermissionMode.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java similarity index 92% rename from packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/PermissionMode.java rename to packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java index 3db5782c6..d960a396e 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/PermissionMode.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java @@ -1,4 +1,4 @@ -package com.alibaba.qwen.code.cli.transport; +package com.alibaba.qwen.code.cli.protocol.data; public enum PermissionMode { DEFAULT("default"), diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java new file mode 100644 index 000000000..14adf7a2f --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java @@ -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 updatedInput; + + public Map getUpdatedInput() { + return updatedInput; + } + + public Allow setUpdatedInput(Map updatedInput) { + this.updatedInput = updatedInput; + return this; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java new file mode 100644 index 000000000..1f54f2341 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java @@ -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"); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java new file mode 100644 index 000000000..17d37ca05 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java new file mode 100644 index 000000000..f4a052697 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java new file mode 100644 index 000000000..ac3e43e79 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java @@ -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 input; + + @JSONField(name = "permission_suggestions") + private List 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 getInput() { + return input; + } + + public void setInput(Map input) { + this.input = input; + } + + public List getPermissionSuggestions() { + return permissionSuggestions; + } + + public void setPermissionSuggestions( + List 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; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java new file mode 100644 index 000000000..66c199632 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java index 5e193e2cd..bce0c03cc 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java @@ -21,34 +21,43 @@ public class CLIControlResponse extends MessageBase { this.response = response; } + public Response createResponse() { + Response response = new Response<>(); + this.setResponse(response); + return response; + } + public static class Response { @JSONField(name = "request_id") private String requestId; - private String subtype; + private String subtype = "success"; R response; public String getRequestId() { return requestId; } - public void setRequestId(String requestId) { + public Response setRequestId(String requestId) { this.requestId = requestId; + return this; } public String getSubtype() { return subtype; } - public void setSubtype(String subtype) { + public Response setSubtype(String subtype) { this.subtype = subtype; + return this; } public R getResponse() { return response; } - public void setResponse(R response) { + public Response setResponse(R response) { this.response = response; + return this; } } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java new file mode 100644 index 000000000..d93a6fb6d --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java new file mode 100644 index 000000000..ea1ad9698 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java index 346b0f1f2..79a210742 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java @@ -1,59 +1,141 @@ package com.alibaba.qwen.code.cli.session; +import java.io.IOException; +import java.util.Optional; + import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONReader.Feature; import com.alibaba.fastjson2.TypeReference; import com.alibaba.qwen.code.cli.protocol.data.Capabilities; +import com.alibaba.qwen.code.cli.protocol.data.PermissionMode; +import com.alibaba.qwen.code.cli.protocol.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.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.SessionCloseException; +import com.alibaba.qwen.code.cli.session.exception.SessionControlException; import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException; -import com.alibaba.qwen.code.cli.session.exception.SessionStartException; import com.alibaba.qwen.code.cli.transport.Transport; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Session { private final Transport transport; - private Capabilities capabilities; + private CLIControlInitializeResponse lastCliControlInitializeResponse; + private SDKSystemMessage lastSdkSystemMessage; private static final Logger log = LoggerFactory.getLogger(Session.class); - public Session(Transport transport) throws SessionStartException { + public Session(Transport transport) throws SessionControlException { if (transport == null || !transport.isAvailable()) { - throw new SessionStartException("Transport is not available"); + throw new SessionControlException("Transport is not available"); } this.transport = transport; start(); } - private void start() throws SessionStartException { + public void start() throws SessionControlException { try { + if (!transport.isAvailable()) { + transport.start(); + } String response = transport.inputWaitForOneLine(CLIControlRequest.create(new CLIControlInitializeRequest()).toString()); - CLIControlResponse cliControlResponse = JSON.parseObject(response, new TypeReference>() {}); - this.capabilities = cliControlResponse.getResponse().getResponse().getCapabilities(); + CLIControlResponse cliControlResponse = JSON.parseObject(response, + new TypeReference>() {}); + this.lastCliControlInitializeResponse = cliControlResponse.getResponse().getResponse(); } catch (Exception e) { - throw new SessionStartException("Failed to initialize the session", e); + throw new SessionControlException("Failed to initialize the session", e); } } - public void close() throws SessionCloseException { + public void interrupt() throws SessionControlException { + if (!isAvailable()) { + throw new SessionControlException("Session is not available"); + } + + try { + transport.inputNoWaitResponse( + new CLIControlRequest().setRequest(new CLIControlInterruptRequest()).toString()); + } catch (Exception e) { + throw new SessionControlException("Failed to interrupt the session", e); + } + } + + public void setModel(String modelName) throws SessionControlException { + if (!isAvailable()) { + throw new SessionControlException("Session is not available"); + } + + CLIControlSetModelRequest cliControlSetModelRequest = new CLIControlSetModelRequest(); + cliControlSetModelRequest.setModel(modelName); + try { + transport.inputNoWaitResponse(new CLIControlRequest().setRequest(cliControlSetModelRequest).toString()); + } catch (Exception e) { + throw new SessionControlException("Failed to set model", e); + } + } + + public void setPermissionMode(PermissionMode permissionMode) throws SessionControlException { + if (!isAvailable()) { + throw new SessionControlException("Session is not available"); + } + + CLIControlSetPermissionModeRequest cliControlSetPermissionModeRequest = new CLIControlSetPermissionModeRequest(); + cliControlSetPermissionModeRequest.setMode(permissionMode.getValue()); + try { + transport.inputNoWaitResponse( + new CLIControlRequest().setRequest(cliControlSetPermissionModeRequest).toString()); + } 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 (!isAvailable()) { + throw new SessionControlException("Session is not available"); + } + + if (StringUtils.isNotBlank(sessionId)) { + transport.getTransportOptions().setResumeSessionId(sessionId); + } + this.start(); + } + + public String getSessionId() { + return Optional.ofNullable(lastSdkSystemMessage).map(SDKSystemMessage::getSessionId).orElse(null); + } + + public void close() throws SessionControlException { try { transport.close(); } catch (Exception e) { - throw new SessionCloseException("Failed to close the session", e); + throw new SessionControlException("Failed to close the session", e); } } + public boolean isAvailable() { + return transport.isAvailable(); + } + public Capabilities getCapabilities() { - return capabilities; + return Optional.ofNullable(lastCliControlInitializeResponse).map(CLIControlInitializeResponse::getCapabilities).orElse(new Capabilities()); } public void sendPrompt(String prompt, SessionEventConsumers sessionEventConsumers) throws SessionSendPromptException { @@ -65,20 +147,33 @@ public class Session { transport.inputWaitForMultiLine(new SDKUserMessage().setContent(prompt).toString(), (line) -> { log.debug("read a message from agent {}", line); JSONObject jsonObject = JSON.parseObject(line); - String messageType = jsonObject.getString("type"); if ("system".equals(messageType)) { - sessionEventConsumers.onSystemMessage(JSON.parseObject(line, SDKSystemMessage.class)); + lastSdkSystemMessage = jsonObject.to(SDKSystemMessage.class); + sessionEventConsumers.onSystemMessage(this, lastSdkSystemMessage); return false; } else if ("assistant".equals(messageType)) { - sessionEventConsumers.onAssistantMessage(JSON.parseObject(line, SDKAssistantMessage.class)); + sessionEventConsumers.onAssistantMessage(this, jsonObject.to(SDKAssistantMessage.class)); + return false; + } else if ("user".equals(messageType)) { + sessionEventConsumers.onUserMessage(this, jsonObject.to(SDKUserMessage.class, Feature.FieldBased)); return false; } else if ("result".equals(messageType)) { - sessionEventConsumers.onResultMessage(JSON.parseObject(line, SDKResultMessage.class)); + sessionEventConsumers.onResultMessage(this, jsonObject.to(SDKResultMessage.class)); return true; + } else if ("control_response".equals(messageType)) { + sessionEventConsumers.onControlResponse(this, jsonObject.to(CLIControlResponse.class)); + 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 processControlRequest(jsonObject, sessionEventConsumers); } else { log.warn("unknown message type: {}", messageType); - sessionEventConsumers.onOtherMessage(line); + sessionEventConsumers.onOtherMessage(this, line); return false; } }); @@ -86,4 +181,53 @@ public class Session { throw new SessionSendPromptException("Failed to send prompt", e); } } + + private boolean processControlRequest(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 e) { + log.error("Failed to process permission response", e); + return false; + } + } else { + CLIControlResponse cliControlResponse = sessionEventConsumers.onControlRequest(this, + jsonObject.to(new TypeReference>() {})); + 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 { + CLIControlRequest permissionRequest = jsonObject.to(new TypeReference>() {}); + Behavior behavior = Optional.ofNullable(sessionEventConsumers.onPermissionRequest(this, permissionRequest)) + .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 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; + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java index 9f9bb6fc1..e2100b5cc 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java @@ -1,15 +1,29 @@ 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.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; public interface SessionEventConsumers { - void onSystemMessage(SDKSystemMessage systemMessage); + void onSystemMessage(Session session, SDKSystemMessage systemMessage); - void onResultMessage(SDKResultMessage resultMessage); + void onResultMessage(Session session, SDKResultMessage resultMessage); - void onAssistantMessage(SDKAssistantMessage assistantMessage); + void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage); - void onOtherMessage(String message); + 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 permissionRequest); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java index 584354d43..9c685e755 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java @@ -1,23 +1,47 @@ 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.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; public class SessionEventSimpleConsumers implements SessionEventConsumers { @Override - public void onSystemMessage(SDKSystemMessage systemMessage) { + public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { } @Override - public void onResultMessage(SDKResultMessage resultMessage) { + public void onResultMessage(Session session, SDKResultMessage resultMessage) { } @Override - public void onAssistantMessage(SDKAssistantMessage assistantMessage) { + public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) { } @Override - public void onOtherMessage(String message) { + 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 permissionRequest) { + return Behavior.defaultBehavior(); } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java deleted file mode 100644 index 3db39e793..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionCloseException.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.alibaba.qwen.code.cli.session.exception; - -public class SessionCloseException extends Exception { - public SessionCloseException() { - } - - public SessionCloseException(String message) { - super(message); - } - - public SessionCloseException(String message, Throwable cause) { - super(message, cause); - } - - public SessionCloseException(Throwable cause) { - super(cause); - } - - public SessionCloseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java new file mode 100644 index 000000000..770d5982c --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java @@ -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); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java deleted file mode 100644 index 9d30f2367..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionStartException.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.alibaba.qwen.code.cli.session.exception; - -public class SessionStartException extends Exception { - public SessionStartException() { - } - - public SessionStartException(String message) { - super(message); - } - - public SessionStartException(String message, Throwable cause) { - super(message, cause); - } - - public SessionStartException(Throwable cause) { - super(cause); - } - - public SessionStartException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java index 5b66cbc90..b3d69ee28 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java @@ -6,6 +6,10 @@ import java.util.concurrent.TimeoutException; import java.util.function.Function; public interface Transport { + TransportOptions getTransportOptions(); + + void start() throws IOException; + void close() throws IOException; boolean isAvailable(); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java index b64df2ec6..b5e6ada6f 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java @@ -3,6 +3,8 @@ package com.alibaba.qwen.code.cli.transport; import java.util.List; import java.util.Map; +import com.alibaba.qwen.code.cli.protocol.data.PermissionMode; + public class TransportOptions implements Cloneable { private String pathToQwenExecutable; private String cwd; @@ -17,6 +19,7 @@ public class TransportOptions implements Cloneable { private Boolean includePartialMessages; private Long turnTimeoutMs; private Long messageTimeoutMs; + private String resumeSessionId; public String getPathToQwenExecutable() { return pathToQwenExecutable; @@ -122,6 +125,14 @@ public class TransportOptions implements Cloneable { this.messageTimeoutMs = messageTimeoutMs; } + public String getResumeSessionId() { + return resumeSessionId; + } + + public void setResumeSessionId(String resumeSessionId) { + this.resumeSessionId = resumeSessionId; + } + @Override public TransportOptions clone() { try { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java index b7d62b5d7..14a0eb0ff 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java @@ -22,10 +22,9 @@ import java.util.function.Function; public class ProcessTransport implements Transport { private static final Logger log = LoggerFactory.getLogger(ProcessTransport.class); - TransportOptionsAdapter transportOptionsAdapter; - - protected final Long turnTimeoutMs; - protected final Long messageTimeoutMs; + private final TransportOptions transportOptions; + protected Long turnTimeoutMs; + protected Long messageTimeoutMs; protected Process process; protected BufferedWriter processInput; @@ -37,13 +36,21 @@ public class ProcessTransport implements Transport { } public ProcessTransport(TransportOptions transportOptions) throws IOException { - this.transportOptionsAdapter = new TransportOptionsAdapter(transportOptions); - turnTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeoutMs(); - messageTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getMessageTimeoutMs(); + this.transportOptions = transportOptions; start(); } - protected void start() throws IOException { + @Override + public TransportOptions getTransportOptions() { + return transportOptions; + } + + @Override + public void start() throws IOException { + TransportOptionsAdapter transportOptionsAdapter = new TransportOptionsAdapter(transportOptions); + this.turnTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeoutMs(); + this.messageTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getMessageTimeoutMs(); + String[] commandArgs = transportOptionsAdapter.buildCommandArgs(); log.debug("trans to command args: {}", transportOptionsAdapter); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java index 4c6b7d48d..1f179f93e 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java @@ -72,6 +72,11 @@ class TransportOptionsAdapter { if (transportOptions.getIncludePartialMessages() != null && transportOptions.getIncludePartialMessages()) { args.add("--include-partial-messages"); } + + if (StringUtils.isNotBlank(transportOptions.getResumeSessionId())) { + args.add("--resume"); + args.add(transportOptions.getResumeSessionId()); + } return args.toArray(new String[] {}); } diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java index 69898b948..51c37c6c8 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java @@ -3,17 +3,23 @@ package com.alibaba.qwen.code.cli.session; import java.io.IOException; import com.alibaba.fastjson2.JSON; +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.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.SessionCloseException; +import com.alibaba.qwen.code.cli.session.exception.SessionControlException; import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException; -import com.alibaba.qwen.code.cli.session.exception.SessionStartException; import com.alibaba.qwen.code.cli.transport.Transport; import com.alibaba.qwen.code.cli.transport.process.ProcessTransport; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,34 +27,111 @@ import org.slf4j.LoggerFactory; class SessionTest { private static final Logger log = LoggerFactory.getLogger(SessionTest.class); + @Test - void sendPrompt() throws IOException, SessionStartException, SessionSendPromptException, SessionCloseException { + void setPermissionModeSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { Transport transport = new ProcessTransport(); Session session = new Session(transport); - session.sendPrompt("hello world", new SessionEventSimpleConsumers() { + + session.setPermissionMode(PermissionMode.YOLO); + session.sendPrompt("in the dir src/test/temp/, create file empty file test.touch", new SessionEventSimpleConsumers()); + + session.setPermissionMode(PermissionMode.PLAN); + session.sendPrompt("rename test.touch to test_rename.touch", new SessionEventSimpleConsumers()); + + session.setPermissionMode(PermissionMode.AUTO_EDIT); + 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 permissionRequest) { + log.info("permissionRequest: {}", permissionRequest); + return new Allow().setUpdatedInput(permissionRequest.getRequest().getInput()); + } + }); + + session.close(); + } + + @Test + void sendPromptAndSetModelSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { + Transport transport = new ProcessTransport(); + Session session = new Session(transport); + + session.setModel("qwen3-coder-flash"); + writeSplitLine("setModel 1 end"); + + session.sendPrompt("hello world", new SessionEventSimpleConsumers()); + writeSplitLine("prompt 1 end"); + + session.setModel("qwen3-coder-plus"); + writeSplitLine("setModel 1 end"); + + session.sendPrompt("查看下当前目录有多少个文件", new SessionEventSimpleConsumers()); + writeSplitLine("prompt 2 end"); + + session.setModel("qwen3-max"); + writeSplitLine("setModel 1 end"); + + session.sendPrompt("查看下当前目录有多少个xml文件", new SessionEventSimpleConsumers()); + writeSplitLine("prompt 3 end"); + + session.close(); + } + + @Test + void sendPromptAndInterruptContinueSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { + Transport transport = new ProcessTransport(); + Session session = new Session(transport); + + SessionEventConsumers sessionEventConsumers = new SessionEventSimpleConsumers() { @Override - public void onSystemMessage(SDKSystemMessage systemMessage) { + public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { log.info("systemMessage: {}", systemMessage); } @Override - public void onResultMessage(SDKResultMessage resultMessage) { + public void onResultMessage(Session session, SDKResultMessage resultMessage) { log.info("resultMessage: {}", resultMessage); } @Override - public void onAssistantMessage(SDKAssistantMessage assistantMessage) { + 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 onOtherMessage(String message) { + public void onControlResponse(Session session, CLIControlResponse cliControlResponse) { + log.info("cliControlResponse: {}", cliControlResponse); + } + + @Override + public void onOtherMessage(Session session, String message) { log.info("otherMessage: {}", message); } - }); + }; + 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}}}"; diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java index 7707c5fda..97e6fe0d1 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/PermissionModeTest.java @@ -1,5 +1,7 @@ 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; diff --git a/packages/sdk-java/todo b/packages/sdk-java/todo new file mode 100644 index 000000000..656489715 --- /dev/null +++ b/packages/sdk-java/todo @@ -0,0 +1,6 @@ +1、event timeout +2、mcp servers +3、errorHandle +4、review QwenCli +https://github.com/QwenLM/qwen-code/tree/main/packages/sdk-typescript#custom-permission-handler + From 5a5dae19874707cfc4ddea5fdcf4213c8e12ed1e Mon Sep 17 00:00:00 2001 From: Alexander Farber Date: Tue, 30 Dec 2025 16:35:34 +0100 Subject: [PATCH 29/65] Add German language support and remove a misleading witty phrase --- packages/cli/src/config/settingsSchema.ts | 1 + packages/cli/src/i18n/locales/de.js | 1073 +++++++++++++++++++++ packages/cli/src/i18n/locales/en.js | 1 - packages/cli/src/i18n/locales/ru.js | 1 - 4 files changed, 1074 insertions(+), 2 deletions(-) create mode 100644 packages/cli/src/i18n/locales/de.js diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 2fe467ba9..5159613b6 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -202,6 +202,7 @@ const SETTINGS_SCHEMA = { { value: 'en', label: 'English' }, { value: 'zh', label: '中文 (Chinese)' }, { value: 'ru', label: 'Русский (Russian)' }, + { value: 'de', label: 'Deutsch (German)' }, ], }, terminalBell: { diff --git a/packages/cli/src/i18n/locales/de.js b/packages/cli/src/i18n/locales/de.js new file mode 100644 index 000000000..4ddcf4c3d --- /dev/null +++ b/packages/cli/src/i18n/locales/de.js @@ -0,0 +1,1073 @@ +/** + * @license + * Copyright 2025 Qwen + * SPDX-License-Identifier: Apache-2.0 + */ + +// German translations for Qwen Code CLI +// Deutsche Ubersetzungen fur Qwen Code CLI + +export default { + // ============================================================================ + // Help / UI Components + // ============================================================================ + 'Basics:': 'Grundlagen:', + 'Add context': 'Kontext hinzufugen', + 'Use {{symbol}} to specify files for context (e.g., {{example}}) to target specific files or folders.': + 'Verwenden Sie {{symbol}}, um Dateien als Kontext anzugeben (z.B. {{example}}), um bestimmte Dateien oder Ordner auszuwahlen.', + '@': '@', + '@src/myFile.ts': '@src/myFile.ts', + 'Shell mode': 'Shell-Modus', + 'YOLO mode': 'YOLO-Modus', + 'plan mode': 'Planungsmodus', + 'auto-accept edits': 'Anderungen automatisch akzeptieren', + 'Accepting edits': 'Anderungen werden akzeptiert', + '(shift + tab to cycle)': '(Umschalt + Tab zum Wechseln)', + 'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).': + 'Shell-Befehle uber {{symbol}} ausfuhren (z.B. {{example1}}) oder naturliche Sprache verwenden (z.B. {{example2}}).', + '!': '!', + '!npm run start': '!npm run start', + 'start server': 'Server starten', + 'Commands:': 'Befehle:', + 'shell command': 'Shell-Befehl', + 'Model Context Protocol command (from external servers)': + 'Model Context Protocol Befehl (von externen Servern)', + 'Keyboard Shortcuts:': 'Tastenkurzel:', + 'Jump through words in the input': 'Worter in der Eingabe uberspringen', + 'Close dialogs, cancel requests, or quit application': + 'Dialoge schliessen, Anfragen abbrechen oder Anwendung beenden', + 'New line': 'Neue Zeile', + 'New line (Alt+Enter works for certain linux distros)': + 'Neue Zeile (Alt+Enter funktioniert bei bestimmten Linux-Distributionen)', + 'Clear the screen': 'Bildschirm loschen', + 'Open input in external editor': 'Eingabe in externem Editor offnen', + 'Send message': 'Nachricht senden', + 'Initializing...': 'Initialisierung...', + 'Connecting to MCP servers... ({{connected}}/{{total}})': + 'Verbindung zu MCP-Servern wird hergestellt... ({{connected}}/{{total}})', + 'Type your message or @path/to/file': 'Nachricht eingeben oder @Pfad/zur/Datei', + "Press 'i' for INSERT mode and 'Esc' for NORMAL mode.": + "Drucken Sie 'i' fur den EINFUGE-Modus und 'Esc' fur den NORMAL-Modus.", + 'Cancel operation / Clear input (double press)': + 'Vorgang abbrechen / Eingabe loschen (doppelt drucken)', + 'Cycle approval modes': 'Genehmigungsmodi durchschalten', + 'Cycle through your prompt history': 'Eingabeverlauf durchblattern', + 'For a full list of shortcuts, see {{docPath}}': + 'Eine vollstandige Liste der Tastenkurzel finden Sie unter {{docPath}}', + 'docs/keyboard-shortcuts.md': 'docs/keyboard-shortcuts.md', + 'for help on Qwen Code': 'fur Hilfe zu Qwen Code', + 'show version info': 'Versionsinformationen anzeigen', + 'submit a bug report': 'Fehlerbericht einreichen', + 'About Qwen Code': 'Uber Qwen Code', + + // ============================================================================ + // System Information Fields + // ============================================================================ + 'CLI Version': 'CLI-Version', + 'Git Commit': 'Git-Commit', + Model: 'Modell', + Sandbox: 'Sandbox', + 'OS Platform': 'Betriebssystem', + 'OS Arch': 'OS-Architektur', + 'OS Release': 'OS-Version', + 'Node.js Version': 'Node.js-Version', + 'NPM Version': 'NPM-Version', + 'Session ID': 'Sitzungs-ID', + 'Auth Method': 'Authentifizierungsmethode', + 'Base URL': 'Basis-URL', + 'Memory Usage': 'Speichernutzung', + 'IDE Client': 'IDE-Client', + + // ============================================================================ + // Commands - General + // ============================================================================ + 'Analyzes the project and creates a tailored QWEN.md file.': + 'Analysiert das Projekt und erstellt eine massgeschneiderte QWEN.md-Datei.', + 'list available Qwen Code tools. Usage: /tools [desc]': + 'Verfugbare Qwen Code Werkzeuge auflisten. Verwendung: /tools [desc]', + 'Available Qwen Code CLI tools:': 'Verfugbare Qwen Code CLI-Werkzeuge:', + 'No tools available': 'Keine Werkzeuge verfugbar', + 'View or change the approval mode for tool usage': + 'Genehmigungsmodus fur Werkzeugnutzung anzeigen oder andern', + 'View or change the language setting': 'Spracheinstellung anzeigen oder andern', + 'change the theme': 'Design andern', + 'Select Theme': 'Design auswahlen', + Preview: 'Vorschau', + '(Use Enter to select, Tab to configure scope)': + '(Enter zum Auswahlen, Tab zum Konfigurieren des Bereichs)', + '(Use Enter to apply scope, Tab to select theme)': + '(Enter zum Anwenden des Bereichs, Tab zum Auswahlen des Designs)', + 'Theme configuration unavailable due to NO_COLOR env variable.': + 'Design-Konfiguration aufgrund der NO_COLOR-Umgebungsvariable nicht verfugbar.', + 'Theme "{{themeName}}" not found.': 'Design "{{themeName}}" nicht gefunden.', + 'Theme "{{themeName}}" not found in selected scope.': + 'Design "{{themeName}}" im ausgewahlten Bereich nicht gefunden.', + 'Clear conversation history and free up context': + 'Gesprachsverlauf loschen und Kontext freigeben', + 'Compresses the context by replacing it with a summary.': + 'Komprimiert den Kontext durch Ersetzen mit einer Zusammenfassung.', + 'open full Qwen Code documentation in your browser': + 'Vollstandige Qwen Code Dokumentation im Browser offnen', + 'Configuration not available.': 'Konfiguration nicht verfugbar.', + 'change the auth method': 'Authentifizierungsmethode andern', + 'Copy the last result or code snippet to clipboard': + 'Letztes Ergebnis oder Codeausschnitt in die Zwischenablage kopieren', + + // ============================================================================ + // Commands - Agents + // ============================================================================ + 'Manage subagents for specialized task delegation.': + 'Unteragenten fur spezialisierte Aufgabendelegation verwalten.', + 'Manage existing subagents (view, edit, delete).': + 'Bestehende Unteragenten verwalten (anzeigen, bearbeiten, loschen).', + 'Create a new subagent with guided setup.': + 'Neuen Unteragenten mit gefuhrter Einrichtung erstellen.', + + // ============================================================================ + // Agents - Management Dialog + // ============================================================================ + Agents: 'Agenten', + 'Choose Action': 'Aktion wahlen', + 'Edit {{name}}': '{{name}} bearbeiten', + 'Edit Tools: {{name}}': 'Werkzeuge bearbeiten: {{name}}', + 'Edit Color: {{name}}': 'Farbe bearbeiten: {{name}}', + 'Delete {{name}}': '{{name}} loschen', + 'Unknown Step': 'Unbekannter Schritt', + 'Esc to close': 'Esc zum Schliessen', + 'Enter to select, ↑↓ to navigate, Esc to close': + 'Enter zum Auswahlen, ↑↓ zum Navigieren, Esc zum Schliessen', + 'Esc to go back': 'Esc zum Zuruckgehen', + 'Enter to confirm, Esc to cancel': 'Enter zum Bestatigen, Esc zum Abbrechen', + 'Enter to select, ↑↓ to navigate, Esc to go back': + 'Enter zum Auswahlen, ↑↓ zum Navigieren, Esc zum Zuruckgehen', + 'Invalid step: {{step}}': 'Ungultiger Schritt: {{step}}', + 'No subagents found.': 'Keine Unteragenten gefunden.', + "Use '/agents create' to create your first subagent.": + "Verwenden Sie '/agents create', um Ihren ersten Unteragenten zu erstellen.", + '(built-in)': '(integriert)', + '(overridden by project level agent)': '(uberschrieben durch Projektagent)', + 'Project Level ({{path}})': 'Projektebene ({{path}})', + 'User Level ({{path}})': 'Benutzerebene ({{path}})', + 'Built-in Agents': 'Integrierte Agenten', + 'Using: {{count}} agents': 'Verwendet: {{count}} Agenten', + 'View Agent': 'Agent anzeigen', + 'Edit Agent': 'Agent bearbeiten', + 'Delete Agent': 'Agent loschen', + Back: 'Zuruck', + 'No agent selected': 'Kein Agent ausgewahlt', + 'File Path: ': 'Dateipfad: ', + 'Tools: ': 'Werkzeuge: ', + 'Color: ': 'Farbe: ', + 'Description:': 'Beschreibung:', + 'System Prompt:': 'System-Prompt:', + 'Open in editor': 'Im Editor offnen', + 'Edit tools': 'Werkzeuge bearbeiten', + 'Edit color': 'Farbe bearbeiten', + '❌ Error:': '❌ Fehler:', + 'Are you sure you want to delete agent "{{name}}"?': + 'Sind Sie sicher, dass Sie den Agenten "{{name}}" loschen mochten?', + // ============================================================================ + // Agents - Creation Wizard + // ============================================================================ + 'Project Level (.qwen/agents/)': 'Projektebene (.qwen/agents/)', + 'User Level (~/.qwen/agents/)': 'Benutzerebene (~/.qwen/agents/)', + '✅ Subagent Created Successfully!': '✅ Unteragent erfolgreich erstellt!', + 'Subagent "{{name}}" has been saved to {{level}} level.': + 'Unteragent "{{name}}" wurde auf {{level}}-Ebene gespeichert.', + 'Name: ': 'Name: ', + 'Location: ': 'Speicherort: ', + '❌ Error saving subagent:': '❌ Fehler beim Speichern des Unteragenten:', + 'Warnings:': 'Warnungen:', + 'Name "{{name}}" already exists at {{level}} level - will overwrite existing subagent': + 'Name "{{name}}" existiert bereits auf {{level}}-Ebene - bestehender Unteragent wird uberschrieben', + 'Name "{{name}}" exists at user level - project level will take precedence': + 'Name "{{name}}" existiert auf Benutzerebene - Projektebene hat Vorrang', + 'Name "{{name}}" exists at project level - existing subagent will take precedence': + 'Name "{{name}}" existiert auf Projektebene - bestehender Unteragent hat Vorrang', + 'Description is over {{length}} characters': + 'Beschreibung ist uber {{length}} Zeichen', + 'System prompt is over {{length}} characters': + 'System-Prompt ist uber {{length}} Zeichen', + // Agents - Creation Wizard Steps + 'Step {{n}}: Choose Location': 'Schritt {{n}}: Speicherort wahlen', + 'Step {{n}}: Choose Generation Method': + 'Schritt {{n}}: Generierungsmethode wahlen', + 'Generate with Qwen Code (Recommended)': + 'Mit Qwen Code generieren (Empfohlen)', + 'Manual Creation': 'Manuelle Erstellung', + 'Describe what this subagent should do and when it should be used. (Be comprehensive for best results)': + 'Beschreiben Sie, was dieser Unteragent tun soll und wann er verwendet werden soll. (Ausfuhrliche Beschreibung fur beste Ergebnisse)', + 'e.g., Expert code reviewer that reviews code based on best practices...': + 'z.B. Experte fur Code-Reviews, der Code nach Best Practices uberpruft...', + 'Generating subagent configuration...': + 'Unteragent-Konfiguration wird generiert...', + 'Failed to generate subagent: {{error}}': + 'Fehler beim Generieren des Unteragenten: {{error}}', + 'Step {{n}}: Describe Your Subagent': 'Schritt {{n}}: Unteragent beschreiben', + 'Step {{n}}: Enter Subagent Name': 'Schritt {{n}}: Unteragent-Name eingeben', + 'Step {{n}}: Enter System Prompt': 'Schritt {{n}}: System-Prompt eingeben', + 'Step {{n}}: Enter Description': 'Schritt {{n}}: Beschreibung eingeben', + // Agents - Tool Selection + 'Step {{n}}: Select Tools': 'Schritt {{n}}: Werkzeuge auswahlen', + 'All Tools (Default)': 'Alle Werkzeuge (Standard)', + 'All Tools': 'Alle Werkzeuge', + 'Read-only Tools': 'Nur-Lese-Werkzeuge', + 'Read & Edit Tools': 'Lese- und Bearbeitungswerkzeuge', + 'Read & Edit & Execution Tools': 'Lese-, Bearbeitungs- und Ausfuhrungswerkzeuge', + 'All tools selected, including MCP tools': + 'Alle Werkzeuge ausgewahlt, einschliesslich MCP-Werkzeuge', + 'Selected tools:': 'Ausgewahlte Werkzeuge:', + 'Read-only tools:': 'Nur-Lese-Werkzeuge:', + 'Edit tools:': 'Bearbeitungswerkzeuge:', + 'Execution tools:': 'Ausfuhrungswerkzeuge:', + 'Step {{n}}: Choose Background Color': 'Schritt {{n}}: Hintergrundfarbe wahlen', + 'Step {{n}}: Confirm and Save': 'Schritt {{n}}: Bestatigen und Speichern', + // Agents - Navigation & Instructions + 'Esc to cancel': 'Esc zum Abbrechen', + 'Press Enter to save, e to save and edit, Esc to go back': + 'Enter zum Speichern, e zum Speichern und Bearbeiten, Esc zum Zuruckgehen', + 'Press Enter to continue, {{navigation}}Esc to {{action}}': + 'Enter zum Fortfahren, {{navigation}}Esc zum {{action}}', + cancel: 'Abbrechen', + 'go back': 'Zuruckgehen', + '↑↓ to navigate, ': '↑↓ zum Navigieren, ', + 'Enter a clear, unique name for this subagent.': + 'Geben Sie einen eindeutigen Namen fur diesen Unteragenten ein.', + 'e.g., Code Reviewer': 'z.B. Code-Reviewer', + 'Name cannot be empty.': 'Name darf nicht leer sein.', + "Write the system prompt that defines this subagent's behavior. Be comprehensive for best results.": + 'Schreiben Sie den System-Prompt, der das Verhalten dieses Unteragenten definiert. Ausfuhrlich fur beste Ergebnisse.', + 'e.g., You are an expert code reviewer...': + 'z.B. Sie sind ein Experte fur Code-Reviews...', + 'System prompt cannot be empty.': 'System-Prompt darf nicht leer sein.', + 'Describe when and how this subagent should be used.': + 'Beschreiben Sie, wann und wie dieser Unteragent verwendet werden soll.', + 'e.g., Reviews code for best practices and potential bugs.': + 'z.B. Uberpruft Code auf Best Practices und mogliche Fehler.', + 'Description cannot be empty.': 'Beschreibung darf nicht leer sein.', + 'Failed to launch editor: {{error}}': 'Fehler beim Starten des Editors: {{error}}', + 'Failed to save and edit subagent: {{error}}': + 'Fehler beim Speichern und Bearbeiten des Unteragenten: {{error}}', + + // ============================================================================ + // Commands - General (continued) + // ============================================================================ + 'View and edit Qwen Code settings': 'Qwen Code Einstellungen anzeigen und bearbeiten', + Settings: 'Einstellungen', + '(Use Enter to select{{tabText}})': '(Enter zum Auswahlen{{tabText}})', + ', Tab to change focus': ', Tab zum Fokuswechsel', + 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.': + 'Um Anderungen zu sehen, muss Qwen Code neu gestartet werden. Drucken Sie r, um jetzt zu beenden und Anderungen anzuwenden.', + 'The command "/{{command}}" is not supported in non-interactive mode.': + 'Der Befehl "/{{command}}" wird im nicht-interaktiven Modus nicht unterstutzt.', + // ============================================================================ + // Settings Labels + // ============================================================================ + 'Vim Mode': 'Vim-Modus', + 'Disable Auto Update': 'Automatische Updates deaktivieren', + 'Enable Prompt Completion': 'Eingabevervollstandigung aktivieren', + 'Debug Keystroke Logging': 'Debug-Protokollierung von Tastatureingaben', + Language: 'Sprache', + 'Output Format': 'Ausgabeformat', + 'Hide Window Title': 'Fenstertitel ausblenden', + 'Show Status in Title': 'Status im Titel anzeigen', + 'Hide Tips': 'Tipps ausblenden', + 'Hide Banner': 'Banner ausblenden', + 'Hide Context Summary': 'Kontextzusammenfassung ausblenden', + 'Hide CWD': 'Arbeitsverzeichnis ausblenden', + 'Hide Sandbox Status': 'Sandbox-Status ausblenden', + 'Hide Model Info': 'Modellinformationen ausblenden', + 'Hide Footer': 'Fusszeile ausblenden', + 'Show Memory Usage': 'Speichernutzung anzeigen', + 'Show Line Numbers': 'Zeilennummern anzeigen', + 'Show Citations': 'Quellenangaben anzeigen', + 'Custom Witty Phrases': 'Benutzerdefinierte Witzige Spruche', + 'Enable Welcome Back': 'Willkommen-zuruck aktivieren', + 'Disable Loading Phrases': 'Ladespruche deaktivieren', + 'Screen Reader Mode': 'Bildschirmleser-Modus', + 'IDE Mode': 'IDE-Modus', + 'Max Session Turns': 'Maximale Sitzungsrunden', + 'Skip Next Speaker Check': 'Nachste-Sprecher-Prufung uberspringen', + 'Skip Loop Detection': 'Schleifenerkennung uberspringen', + 'Skip Startup Context': 'Startkontext uberspringen', + 'Enable OpenAI Logging': 'OpenAI-Protokollierung aktivieren', + 'OpenAI Logging Directory': 'OpenAI-Protokollierungsverzeichnis', + Timeout: 'Zeitlimit', + 'Max Retries': 'Maximale Wiederholungen', + 'Disable Cache Control': 'Cache-Steuerung deaktivieren', + 'Memory Discovery Max Dirs': 'Maximale Verzeichnisse fur Speichererkennung', + 'Load Memory From Include Directories': + 'Speicher aus Include-Verzeichnissen laden', + 'Respect .gitignore': '.gitignore beachten', + 'Respect .qwenignore': '.qwenignore beachten', + 'Enable Recursive File Search': 'Rekursive Dateisuche aktivieren', + 'Disable Fuzzy Search': 'Unscharfe Suche deaktivieren', + 'Enable Interactive Shell': 'Interaktive Shell aktivieren', + 'Show Color': 'Farbe anzeigen', + 'Auto Accept': 'Automatisch akzeptieren', + 'Use Ripgrep': 'Ripgrep verwenden', + 'Use Builtin Ripgrep': 'Integriertes Ripgrep verwenden', + 'Enable Tool Output Truncation': 'Werkzeugausgabe-Kurzung aktivieren', + 'Tool Output Truncation Threshold': 'Schwellenwert fur Werkzeugausgabe-Kurzung', + 'Tool Output Truncation Lines': 'Zeilen fur Werkzeugausgabe-Kurzung', + 'Folder Trust': 'Ordnervertrauen', + 'Vision Model Preview': 'Vision-Modell-Vorschau', + 'Tool Schema Compliance': 'Werkzeug-Schema-Konformitat', + // Settings enum options + 'Auto (detect from system)': 'Automatisch (vom System erkennen)', + Text: 'Text', + JSON: 'JSON', + Plan: 'Plan', + Default: 'Standard', + 'Auto Edit': 'Automatisch bearbeiten', + YOLO: 'YOLO', + 'toggle vim mode on/off': 'Vim-Modus ein-/ausschalten', + 'check session stats. Usage: /stats [model|tools]': + 'Sitzungsstatistiken prufen. Verwendung: /stats [model|tools]', + 'Show model-specific usage statistics.': + 'Modellspezifische Nutzungsstatistiken anzeigen.', + 'Show tool-specific usage statistics.': + 'Werkzeugspezifische Nutzungsstatistiken anzeigen.', + 'exit the cli': 'CLI beenden', + 'list configured MCP servers and tools, or authenticate with OAuth-enabled servers': + 'Konfigurierte MCP-Server und Werkzeuge auflisten oder mit OAuth-fahigen Servern authentifizieren', + 'Manage workspace directories': 'Arbeitsbereichsverzeichnisse verwalten', + 'Add directories to the workspace. Use comma to separate multiple paths': + 'Verzeichnisse zum Arbeitsbereich hinzufugen. Komma zum Trennen mehrerer Pfade verwenden', + 'Show all directories in the workspace': + 'Alle Verzeichnisse im Arbeitsbereich anzeigen', + 'set external editor preference': 'Externen Editor festlegen', + 'Manage extensions': 'Erweiterungen verwalten', + 'List active extensions': 'Aktive Erweiterungen auflisten', + 'Update extensions. Usage: update |--all': + 'Erweiterungen aktualisieren. Verwendung: update |--all', + 'manage IDE integration': 'IDE-Integration verwalten', + 'check status of IDE integration': 'Status der IDE-Integration prufen', + 'install required IDE companion for {{ideName}}': + 'Erforderlichen IDE-Begleiter fur {{ideName}} installieren', + 'enable IDE integration': 'IDE-Integration aktivieren', + 'disable IDE integration': 'IDE-Integration deaktivieren', + 'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.': + 'IDE-Integration wird in Ihrer aktuellen Umgebung nicht unterstutzt. Um diese Funktion zu nutzen, fuhren Sie Qwen Code in einer dieser unterstutzten IDEs aus: VS Code oder VS Code-Forks.', + 'Set up GitHub Actions': 'GitHub Actions einrichten', + 'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf, Trae)': + 'Terminal-Tastenbelegungen fur mehrzeilige Eingabe konfigurieren (VS Code, Cursor, Windsurf, Trae)', + 'Please restart your terminal for the changes to take effect.': + 'Bitte starten Sie Ihr Terminal neu, damit die Anderungen wirksam werden.', + 'Failed to configure terminal: {{error}}': + 'Fehler beim Konfigurieren des Terminals: {{error}}', + 'Could not determine {{terminalName}} config path on Windows: APPDATA environment variable is not set.': + 'Konnte {{terminalName}}-Konfigurationspfad unter Windows nicht ermitteln: APPDATA-Umgebungsvariable ist nicht gesetzt.', + '{{terminalName}} keybindings.json exists but is not a valid JSON array. Please fix the file manually or delete it to allow automatic configuration.': + '{{terminalName}} keybindings.json existiert, ist aber kein gultiges JSON-Array. Bitte korrigieren Sie die Datei manuell oder loschen Sie sie, um automatische Konfiguration zu ermoglichen.', + 'File: {{file}}': 'Datei: {{file}}', + 'Failed to parse {{terminalName}} keybindings.json. The file contains invalid JSON. Please fix the file manually or delete it to allow automatic configuration.': + 'Fehler beim Parsen von {{terminalName}} keybindings.json. Die Datei enthalt ungultiges JSON. Bitte korrigieren Sie die Datei manuell oder loschen Sie sie, um automatische Konfiguration zu ermoglichen.', + 'Error: {{error}}': 'Fehler: {{error}}', + 'Shift+Enter binding already exists': 'Umschalt+Enter-Belegung existiert bereits', + 'Ctrl+Enter binding already exists': 'Strg+Enter-Belegung existiert bereits', + 'Existing keybindings detected. Will not modify to avoid conflicts.': + 'Bestehende Tastenbelegungen erkannt. Keine Anderungen, um Konflikte zu vermeiden.', + 'Please check and modify manually if needed: {{file}}': + 'Bitte prufen und bei Bedarf manuell andern: {{file}}', + 'Added Shift+Enter and Ctrl+Enter keybindings to {{terminalName}}.': + 'Umschalt+Enter und Strg+Enter Tastenbelegungen zu {{terminalName}} hinzugefugt.', + 'Modified: {{file}}': 'Geandert: {{file}}', + '{{terminalName}} keybindings already configured.': + '{{terminalName}}-Tastenbelegungen bereits konfiguriert.', + 'Failed to configure {{terminalName}}.': + 'Fehler beim Konfigurieren von {{terminalName}}.', + 'Your terminal is already configured for an optimal experience with multiline input (Shift+Enter and Ctrl+Enter).': + 'Ihr Terminal ist bereits fur optimale Erfahrung mit mehrzeiliger Eingabe konfiguriert (Umschalt+Enter und Strg+Enter).', + 'Could not detect terminal type. Supported terminals: VS Code, Cursor, Windsurf, and Trae.': + 'Terminal-Typ konnte nicht erkannt werden. Unterstutzte Terminals: VS Code, Cursor, Windsurf und Trae.', + 'Terminal "{{terminal}}" is not supported yet.': + 'Terminal "{{terminal}}" wird noch nicht unterstutzt.', + + // ============================================================================ + // Commands - Language + // ============================================================================ + 'Invalid language. Available: en-US, zh-CN': + 'Ungultige Sprache. Verfugbar: en-US, zh-CN', + 'Language subcommands do not accept additional arguments.': + 'Sprach-Unterbefehle akzeptieren keine zusatzlichen Argumente.', + 'Current UI language: {{lang}}': 'Aktuelle UI-Sprache: {{lang}}', + 'Current LLM output language: {{lang}}': + 'Aktuelle LLM-Ausgabesprache: {{lang}}', + 'LLM output language not set': 'LLM-Ausgabesprache nicht festgelegt', + 'Set UI language': 'UI-Sprache festlegen', + 'Set LLM output language': 'LLM-Ausgabesprache festlegen', + 'Usage: /language ui [zh-CN|en-US]': 'Verwendung: /language ui [zh-CN|en-US]', + 'Usage: /language output ': 'Verwendung: /language output ', + 'Example: /language output 中文': 'Beispiel: /language output Deutsch', + 'Example: /language output English': 'Beispiel: /language output English', + 'Example: /language output 日本語': 'Beispiel: /language output Japanisch', + 'UI language changed to {{lang}}': 'UI-Sprache geandert zu {{lang}}', + 'LLM output language rule file generated at {{path}}': + 'LLM-Ausgabesprach-Regeldatei generiert unter {{path}}', + 'Please restart the application for the changes to take effect.': + 'Bitte starten Sie die Anwendung neu, damit die Anderungen wirksam werden.', + 'Failed to generate LLM output language rule file: {{error}}': + 'Fehler beim Generieren der LLM-Ausgabesprach-Regeldatei: {{error}}', + 'Invalid command. Available subcommands:': + 'Ungultiger Befehl. Verfugbare Unterbefehle:', + 'Available subcommands:': 'Verfugbare Unterbefehle:', + 'To request additional UI language packs, please open an issue on GitHub.': + 'Um zusatzliche UI-Sprachpakete anzufordern, offnen Sie bitte ein Issue auf GitHub.', + 'Available options:': 'Verfugbare Optionen:', + ' - zh-CN: Simplified Chinese': ' - zh-CN: Vereinfachtes Chinesisch', + ' - en-US: English': ' - en-US: Englisch', + 'Set UI language to Simplified Chinese (zh-CN)': + 'UI-Sprache auf Vereinfachtes Chinesisch (zh-CN) setzen', + 'Set UI language to English (en-US)': 'UI-Sprache auf Englisch (en-US) setzen', + + // ============================================================================ + // Commands - Approval Mode + // ============================================================================ + 'Approval Mode': 'Genehmigungsmodus', + 'Current approval mode: {{mode}}': 'Aktueller Genehmigungsmodus: {{mode}}', + 'Available approval modes:': 'Verfugbare Genehmigungsmodi:', + 'Approval mode changed to: {{mode}}': 'Genehmigungsmodus geandert zu: {{mode}}', + 'Approval mode changed to: {{mode}} (saved to {{scope}} settings{{location}})': + 'Genehmigungsmodus geandert zu: {{mode}} (gespeichert in {{scope}} Einstellungen{{location}})', + 'Usage: /approval-mode [--session|--user|--project]': + 'Verwendung: /approval-mode [--session|--user|--project]', + + 'Scope subcommands do not accept additional arguments.': + 'Bereichs-Unterbefehle akzeptieren keine zusatzlichen Argumente.', + 'Plan mode - Analyze only, do not modify files or execute commands': + 'Planungsmodus - Nur analysieren, keine Dateien andern oder Befehle ausfuhren', + 'Default mode - Require approval for file edits or shell commands': + 'Standardmodus - Genehmigung fur Dateibearbeitungen oder Shell-Befehle erforderlich', + 'Auto-edit mode - Automatically approve file edits': + 'Automatischer Bearbeitungsmodus - Dateibearbeitungen automatisch genehmigen', + 'YOLO mode - Automatically approve all tools': + 'YOLO-Modus - Alle Werkzeuge automatisch genehmigen', + '{{mode}} mode': '{{mode}}-Modus', + 'Settings service is not available; unable to persist the approval mode.': + 'Einstellungsdienst nicht verfugbar; Genehmigungsmodus kann nicht gespeichert werden.', + 'Failed to save approval mode: {{error}}': + 'Fehler beim Speichern des Genehmigungsmodus: {{error}}', + 'Failed to change approval mode: {{error}}': + 'Fehler beim Andern des Genehmigungsmodus: {{error}}', + 'Apply to current session only (temporary)': + 'Nur auf aktuelle Sitzung anwenden (temporar)', + 'Persist for this project/workspace': 'Fur dieses Projekt/Arbeitsbereich speichern', + 'Persist for this user on this machine': + 'Fur diesen Benutzer auf diesem Computer speichern', + 'Analyze only, do not modify files or execute commands': + 'Nur analysieren, keine Dateien andern oder Befehle ausfuhren', + 'Require approval for file edits or shell commands': + 'Genehmigung fur Dateibearbeitungen oder Shell-Befehle erforderlich', + 'Automatically approve file edits': 'Dateibearbeitungen automatisch genehmigen', + 'Automatically approve all tools': 'Alle Werkzeuge automatisch genehmigen', + 'Workspace approval mode exists and takes priority. User-level change will have no effect.': + 'Arbeitsbereich-Genehmigungsmodus existiert und hat Vorrang. Benutzerebene-Anderung hat keine Wirkung.', + '(Use Enter to select, Tab to change focus)': + '(Enter zum Auswahlen, Tab zum Fokuswechsel)', + 'Apply To': 'Anwenden auf', + 'User Settings': 'Benutzereinstellungen', + 'Workspace Settings': 'Arbeitsbereich-Einstellungen', + + // ============================================================================ + // Commands - Memory + // ============================================================================ + 'Commands for interacting with memory.': + 'Befehle fur die Interaktion mit dem Speicher.', + 'Show the current memory contents.': 'Aktuellen Speicherinhalt anzeigen.', + 'Show project-level memory contents.': 'Projektebene-Speicherinhalt anzeigen.', + 'Show global memory contents.': 'Globalen Speicherinhalt anzeigen.', + 'Add content to project-level memory.': + 'Inhalt zum Projektebene-Speicher hinzufugen.', + 'Add content to global memory.': 'Inhalt zum globalen Speicher hinzufugen.', + 'Refresh the memory from the source.': 'Speicher aus der Quelle aktualisieren.', + 'Usage: /memory add --project ': + 'Verwendung: /memory add --project ', + 'Usage: /memory add --global ': + 'Verwendung: /memory add --global ', + 'Attempting to save to project memory: "{{text}}"': + 'Versuche im Projektspeicher zu speichern: "{{text}}"', + 'Attempting to save to global memory: "{{text}}"': + 'Versuche im globalen Speicher zu speichern: "{{text}}"', + 'Current memory content from {{count}} file(s):': + 'Aktueller Speicherinhalt aus {{count}} Datei(en):', + 'Memory is currently empty.': 'Speicher ist derzeit leer.', + 'Project memory file not found or is currently empty.': + 'Projektspeicherdatei nicht gefunden oder derzeit leer.', + 'Global memory file not found or is currently empty.': + 'Globale Speicherdatei nicht gefunden oder derzeit leer.', + 'Global memory is currently empty.': 'Globaler Speicher ist derzeit leer.', + 'Global memory content:\n\n---\n{{content}}\n---': + 'Globaler Speicherinhalt:\n\n---\n{{content}}\n---', + 'Project memory content from {{path}}:\n\n---\n{{content}}\n---': + 'Projektspeicherinhalt von {{path}}:\n\n---\n{{content}}\n---', + 'Project memory is currently empty.': 'Projektspeicher ist derzeit leer.', + 'Refreshing memory from source files...': + 'Speicher wird aus Quelldateien aktualisiert...', + 'Add content to the memory. Use --global for global memory or --project for project memory.': + 'Inhalt zum Speicher hinzufugen. --global fur globalen Speicher oder --project fur Projektspeicher verwenden.', + 'Usage: /memory add [--global|--project] ': + 'Verwendung: /memory add [--global|--project] ', + 'Attempting to save to memory {{scope}}: "{{fact}}"': + 'Versuche im Speicher {{scope}} zu speichern: "{{fact}}"', + + // ============================================================================ + // Commands - MCP + // ============================================================================ + 'Authenticate with an OAuth-enabled MCP server': + 'Mit einem OAuth-fahigen MCP-Server authentifizieren', + 'List configured MCP servers and tools': + 'Konfigurierte MCP-Server und Werkzeuge auflisten', + 'Restarts MCP servers.': 'MCP-Server neu starten.', + 'Config not loaded.': 'Konfiguration nicht geladen.', + 'Could not retrieve tool registry.': 'Werkzeugregister konnte nicht abgerufen werden.', + 'No MCP servers configured with OAuth authentication.': + 'Keine MCP-Server mit OAuth-Authentifizierung konfiguriert.', + 'MCP servers with OAuth authentication:': + 'MCP-Server mit OAuth-Authentifizierung:', + 'Use /mcp auth to authenticate.': + 'Verwenden Sie /mcp auth zur Authentifizierung.', + "MCP server '{{name}}' not found.": "MCP-Server '{{name}}' nicht gefunden.", + "Successfully authenticated and refreshed tools for '{{name}}'.": + "Erfolgreich authentifiziert und Werkzeuge fur '{{name}}' aktualisiert.", + "Failed to authenticate with MCP server '{{name}}': {{error}}": + "Authentifizierung mit MCP-Server '{{name}}' fehlgeschlagen: {{error}}", + "Re-discovering tools from '{{name}}'...": + "Werkzeuge von '{{name}}' werden neu erkannt...", + + // ============================================================================ + // Commands - Chat + // ============================================================================ + 'Manage conversation history.': 'Gesprachsverlauf verwalten.', + 'List saved conversation checkpoints': 'Gespeicherte Gesprachspruefpunkte auflisten', + 'No saved conversation checkpoints found.': + 'Keine gespeicherten Gesprachsprufpunkte gefunden.', + 'List of saved conversations:': 'Liste gespeicherter Gesprache:', + 'Note: Newest last, oldest first': 'Hinweis: Neueste zuletzt, alteste zuerst', + 'Save the current conversation as a checkpoint. Usage: /chat save ': + 'Aktuelles Gesprach als Prufpunkt speichern. Verwendung: /chat save ', + 'Missing tag. Usage: /chat save ': + 'Tag fehlt. Verwendung: /chat save ', + 'Delete a conversation checkpoint. Usage: /chat delete ': + 'Gesprachsprufpunkt loschen. Verwendung: /chat delete ', + 'Missing tag. Usage: /chat delete ': + 'Tag fehlt. Verwendung: /chat delete ', + "Conversation checkpoint '{{tag}}' has been deleted.": + "Gesprachsprufpunkt '{{tag}}' wurde geloscht.", + "Error: No checkpoint found with tag '{{tag}}'.": + "Fehler: Kein Prufpunkt mit Tag '{{tag}}' gefunden.", + 'Resume a conversation from a checkpoint. Usage: /chat resume ': + 'Gesprach von einem Prufpunkt fortsetzen. Verwendung: /chat resume ', + 'Missing tag. Usage: /chat resume ': + 'Tag fehlt. Verwendung: /chat resume ', + 'No saved checkpoint found with tag: {{tag}}.': + 'Kein gespeicherter Prufpunkt mit Tag gefunden: {{tag}}.', + 'A checkpoint with the tag {{tag}} already exists. Do you want to overwrite it?': + 'Ein Prufpunkt mit dem Tag {{tag}} existiert bereits. Mochten Sie ihn uberschreiben?', + 'No chat client available to save conversation.': + 'Kein Chat-Client verfugbar, um Gesprach zu speichern.', + 'Conversation checkpoint saved with tag: {{tag}}.': + 'Gesprachsprufpunkt gespeichert mit Tag: {{tag}}.', + 'No conversation found to save.': 'Kein Gesprach zum Speichern gefunden.', + 'No chat client available to share conversation.': + 'Kein Chat-Client verfugbar, um Gesprach zu teilen.', + 'Invalid file format. Only .md and .json are supported.': + 'Ungultiges Dateiformat. Nur .md und .json werden unterstutzt.', + 'Error sharing conversation: {{error}}': + 'Fehler beim Teilen des Gesprachs: {{error}}', + 'Conversation shared to {{filePath}}': 'Gesprach geteilt nach {{filePath}}', + 'No conversation found to share.': 'Kein Gesprach zum Teilen gefunden.', + 'Share the current conversation to a markdown or json file. Usage: /chat share ': + 'Aktuelles Gesprach in eine Markdown- oder JSON-Datei teilen. Verwendung: /chat share ', + + // ============================================================================ + // Commands - Summary + // ============================================================================ + 'Generate a project summary and save it to .qwen/PROJECT_SUMMARY.md': + 'Projektzusammenfassung generieren und in .qwen/PROJECT_SUMMARY.md speichern', + 'No chat client available to generate summary.': + 'Kein Chat-Client verfugbar, um Zusammenfassung zu generieren.', + 'Already generating summary, wait for previous request to complete': + 'Zusammenfassung wird bereits generiert, warten Sie auf Abschluss der vorherigen Anfrage', + 'No conversation found to summarize.': 'Kein Gesprach zum Zusammenfassen gefunden.', + 'Failed to generate project context summary: {{error}}': + 'Fehler beim Generieren der Projektkontextzusammenfassung: {{error}}', + 'Saved project summary to {{filePathForDisplay}}.': + 'Projektzusammenfassung gespeichert unter {{filePathForDisplay}}.', + 'Saving project summary...': 'Projektzusammenfassung wird gespeichert...', + 'Generating project summary...': 'Projektzusammenfassung wird generiert...', + 'Failed to generate summary - no text content received from LLM response': + 'Fehler beim Generieren der Zusammenfassung - kein Textinhalt von LLM-Antwort erhalten', + + // ============================================================================ + // Commands - Model + // ============================================================================ + 'Switch the model for this session': 'Modell fur diese Sitzung wechseln', + 'Content generator configuration not available.': + 'Inhaltsgenerator-Konfiguration nicht verfugbar.', + 'Authentication type not available.': 'Authentifizierungstyp nicht verfugbar.', + 'No models available for the current authentication type ({{authType}}).': + 'Keine Modelle fur den aktuellen Authentifizierungstyp ({{authType}}) verfugbar.', + + // ============================================================================ + // Commands - Clear + // ============================================================================ + 'Starting a new session, resetting chat, and clearing terminal.': + 'Neue Sitzung wird gestartet, Chat wird zuruckgesetzt und Terminal wird geloscht.', + 'Starting a new session and clearing.': + 'Neue Sitzung wird gestartet und geloscht.', + + // ============================================================================ + // Commands - Compress + // ============================================================================ + 'Already compressing, wait for previous request to complete': + 'Komprimierung lauft bereits, warten Sie auf Abschluss der vorherigen Anfrage', + 'Failed to compress chat history.': 'Fehler beim Komprimieren des Chatverlaufs.', + 'Failed to compress chat history: {{error}}': + 'Fehler beim Komprimieren des Chatverlaufs: {{error}}', + 'Compressing chat history': 'Chatverlauf wird komprimiert', + 'Chat history compressed from {{originalTokens}} to {{newTokens}} tokens.': + 'Chatverlauf komprimiert von {{originalTokens}} auf {{newTokens}} Token.', + 'Compression was not beneficial for this history size.': + 'Komprimierung war fur diese Verlaufsgross nicht vorteilhaft.', + 'Chat history compression did not reduce size. This may indicate issues with the compression prompt.': + 'Chatverlauf-Komprimierung hat die Grosse nicht reduziert. Dies kann auf Probleme mit dem Komprimierungs-Prompt hindeuten.', + 'Could not compress chat history due to a token counting error.': + 'Chatverlauf konnte aufgrund eines Token-Zahlfehlers nicht komprimiert werden.', + 'Chat history is already compressed.': 'Chatverlauf ist bereits komprimiert.', + + // ============================================================================ + // Commands - Directory + // ============================================================================ + 'Configuration is not available.': 'Konfiguration ist nicht verfugbar.', + 'Please provide at least one path to add.': + 'Bitte geben Sie mindestens einen Pfad zum Hinzufugen an.', + 'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.': + 'Der Befehl /directory add wird in restriktiven Sandbox-Profilen nicht unterstutzt. Bitte verwenden Sie --include-directories beim Starten der Sitzung.', + "Error adding '{{path}}': {{error}}": "Fehler beim Hinzufugen von '{{path}}': {{error}}", + 'Successfully added QWEN.md files from the following directories if there are:\n- {{directories}}': + 'QWEN.md-Dateien aus folgenden Verzeichnissen erfolgreich hinzugefugt, falls vorhanden:\n- {{directories}}', + 'Error refreshing memory: {{error}}': 'Fehler beim Aktualisieren des Speichers: {{error}}', + 'Successfully added directories:\n- {{directories}}': + 'Verzeichnisse erfolgreich hinzugefugt:\n- {{directories}}', + 'Current workspace directories:\n{{directories}}': + 'Aktuelle Arbeitsbereichsverzeichnisse:\n{{directories}}', + + // ============================================================================ + // Commands - Docs + // ============================================================================ + 'Please open the following URL in your browser to view the documentation:\n{{url}}': + 'Bitte offnen Sie folgende URL in Ihrem Browser, um die Dokumentation anzusehen:\n{{url}}', + 'Opening documentation in your browser: {{url}}': + 'Dokumentation wird in Ihrem Browser geoffnet: {{url}}', + + // ============================================================================ + // Dialogs - Tool Confirmation + // ============================================================================ + 'Do you want to proceed?': 'Mochten Sie fortfahren?', + 'Yes, allow once': 'Ja, einmal erlauben', + 'Allow always': 'Immer erlauben', + No: 'Nein', + 'No (esc)': 'Nein (Esc)', + 'Yes, allow always for this session': 'Ja, fur diese Sitzung immer erlauben', + 'Modify in progress:': 'Anderung in Bearbeitung:', + 'Save and close external editor to continue': + 'Speichern und externen Editor schliessen, um fortzufahren', + 'Apply this change?': 'Diese Anderung anwenden?', + 'Yes, allow always': 'Ja, immer erlauben', + 'Modify with external editor': 'Mit externem Editor bearbeiten', + 'No, suggest changes (esc)': 'Nein, Anderungen vorschlagen (Esc)', + "Allow execution of: '{{command}}'?": "Ausfuhrung erlauben von: '{{command}}'?", + 'Yes, allow always ...': 'Ja, immer erlauben ...', + 'Yes, and auto-accept edits': 'Ja, und Anderungen automatisch akzeptieren', + 'Yes, and manually approve edits': 'Ja, und Anderungen manuell genehmigen', + 'No, keep planning (esc)': 'Nein, weiter planen (Esc)', + 'URLs to fetch:': 'Abzurufende URLs:', + 'MCP Server: {{server}}': 'MCP-Server: {{server}}', + 'Tool: {{tool}}': 'Werkzeug: {{tool}}', + 'Allow execution of MCP tool "{{tool}}" from server "{{server}}"?': + 'Ausfuhrung des MCP-Werkzeugs "{{tool}}" von Server "{{server}}" erlauben?', + 'Yes, always allow tool "{{tool}}" from server "{{server}}"': + 'Ja, Werkzeug "{{tool}}" von Server "{{server}}" immer erlauben', + 'Yes, always allow all tools from server "{{server}}"': + 'Ja, alle Werkzeuge von Server "{{server}}" immer erlauben', + + // ============================================================================ + // Dialogs - Shell Confirmation + // ============================================================================ + 'Shell Command Execution': 'Shell-Befehlsausfuhrung', + 'A custom command wants to run the following shell commands:': + 'Ein benutzerdefinierter Befehl mochte folgende Shell-Befehle ausfuhren:', + + // ============================================================================ + // Dialogs - Pro Quota + // ============================================================================ + 'Pro quota limit reached for {{model}}.': + 'Pro-Kontingentlimit fur {{model}} erreicht.', + 'Change auth (executes the /auth command)': + 'Authentifizierung andern (fuhrt den /auth-Befehl aus)', + 'Continue with {{model}}': 'Mit {{model}} fortfahren', + + // ============================================================================ + // Dialogs - Welcome Back + // ============================================================================ + 'Current Plan:': 'Aktueller Plan:', + 'Progress: {{done}}/{{total}} tasks completed': + 'Fortschritt: {{done}}/{{total}} Aufgaben abgeschlossen', + ', {{inProgress}} in progress': ', {{inProgress}} in Bearbeitung', + 'Pending Tasks:': 'Ausstehende Aufgaben:', + 'What would you like to do?': 'Was mochten Sie tun?', + 'Choose how to proceed with your session:': + 'Wahlen Sie, wie Sie mit Ihrer Sitzung fortfahren mochten:', + 'Start new chat session': 'Neue Chat-Sitzung starten', + 'Continue previous conversation': 'Vorheriges Gesprach fortsetzen', + '👋 Welcome back! (Last updated: {{timeAgo}})': + '👋 Willkommen zuruck! (Zuletzt aktualisiert: {{timeAgo}})', + '🎯 Overall Goal:': '🎯 Gesamtziel:', + + // ============================================================================ + // Dialogs - Auth + // ============================================================================ + 'Get started': 'Loslegen', + 'How would you like to authenticate for this project?': + 'Wie mochten Sie sich fur dieses Projekt authentifizieren?', + 'OpenAI API key is required to use OpenAI authentication.': + 'OpenAI API-Schlussel ist fur die OpenAI-Authentifizierung erforderlich.', + 'You must select an auth method to proceed. Press Ctrl+C again to exit.': + 'Sie mussen eine Authentifizierungsmethode wahlen, um fortzufahren. Drucken Sie erneut Strg+C zum Beenden.', + '(Use Enter to Set Auth)': '(Enter zum Festlegen der Authentifizierung)', + 'Terms of Services and Privacy Notice for Qwen Code': + 'Nutzungsbedingungen und Datenschutzhinweis fur Qwen Code', + 'Qwen OAuth': 'Qwen OAuth', + OpenAI: 'OpenAI', + 'Failed to login. Message: {{message}}': + 'Anmeldung fehlgeschlagen. Meldung: {{message}}', + 'Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.': + 'Authentifizierung ist auf {{enforcedType}} festgelegt, aber Sie verwenden derzeit {{currentType}}.', + 'Qwen OAuth authentication timed out. Please try again.': + 'Qwen OAuth-Authentifizierung abgelaufen. Bitte versuchen Sie es erneut.', + 'Qwen OAuth authentication cancelled.': + 'Qwen OAuth-Authentifizierung abgebrochen.', + 'Qwen OAuth Authentication': 'Qwen OAuth-Authentifizierung', + 'Please visit this URL to authorize:': 'Bitte besuchen Sie diese URL zur Autorisierung:', + 'Or scan the QR code below:': 'Oder scannen Sie den QR-Code unten:', + 'Waiting for authorization': 'Warten auf Autorisierung', + 'Time remaining:': 'Verbleibende Zeit:', + '(Press ESC or CTRL+C to cancel)': '(ESC oder STRG+C zum Abbrechen drucken)', + 'Qwen OAuth Authentication Timeout': 'Qwen OAuth-Authentifizierung abgelaufen', + 'OAuth token expired (over {{seconds}} seconds). Please select authentication method again.': + 'OAuth-Token abgelaufen (uber {{seconds}} Sekunden). Bitte wahlen Sie erneut eine Authentifizierungsmethode.', + 'Press any key to return to authentication type selection.': + 'Drucken Sie eine beliebige Taste, um zur Authentifizierungstypauswahl zuruckzukehren.', + 'Waiting for Qwen OAuth authentication...': + 'Warten auf Qwen OAuth-Authentifizierung...', + 'Note: Your existing API key in settings.json will not be cleared when using Qwen OAuth. You can switch back to OpenAI authentication later if needed.': + 'Hinweis: Ihr bestehender API-Schlussel in settings.json wird bei Verwendung von Qwen OAuth nicht geloscht. Sie konnen spater bei Bedarf zur OpenAI-Authentifizierung zuruckwechseln.', + 'Authentication timed out. Please try again.': + 'Authentifizierung abgelaufen. Bitte versuchen Sie es erneut.', + 'Waiting for auth... (Press ESC or CTRL+C to cancel)': + 'Warten auf Authentifizierung... (ESC oder STRG+C zum Abbrechen drucken)', + 'Failed to authenticate. Message: {{message}}': + 'Authentifizierung fehlgeschlagen. Meldung: {{message}}', + 'Authenticated successfully with {{authType}} credentials.': + 'Erfolgreich mit {{authType}}-Anmeldedaten authentifiziert.', + 'Invalid QWEN_DEFAULT_AUTH_TYPE value: "{{value}}". Valid values are: {{validValues}}': + 'Ungultiger QWEN_DEFAULT_AUTH_TYPE-Wert: "{{value}}". Gultige Werte sind: {{validValues}}', + 'OpenAI Configuration Required': 'OpenAI-Konfiguration erforderlich', + 'Please enter your OpenAI configuration. You can get an API key from': + 'Bitte geben Sie Ihre OpenAI-Konfiguration ein. Sie konnen einen API-Schlussel erhalten von', + 'API Key:': 'API-Schlussel:', + 'Invalid credentials: {{errorMessage}}': + 'Ungultige Anmeldedaten: {{errorMessage}}', + 'Failed to validate credentials': 'Anmeldedaten konnten nicht validiert werden', + 'Press Enter to continue, Tab/↑↓ to navigate, Esc to cancel': + 'Enter zum Fortfahren, Tab/↑↓ zum Navigieren, Esc zum Abbrechen', + + // ============================================================================ + // Dialogs - Model + // ============================================================================ + 'Select Model': 'Modell auswahlen', + '(Press Esc to close)': '(Esc zum Schliessen drucken)', + 'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)': + 'Das neueste Qwen Coder Modell von Alibaba Cloud ModelStudio (Version: qwen3-coder-plus-2025-09-23)', + 'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)': + 'Das neueste Qwen Vision Modell von Alibaba Cloud ModelStudio (Version: qwen3-vl-plus-2025-09-23)', + + // ============================================================================ + // Dialogs - Permissions + // ============================================================================ + 'Manage folder trust settings': 'Ordnervertrauenseinstellungen verwalten', + + // ============================================================================ + // Status Bar + // ============================================================================ + 'Using:': 'Verwendet:', + '{{count}} open file': '{{count}} geoffnete Datei', + '{{count}} open files': '{{count}} geoffnete Dateien', + '(ctrl+g to view)': '(Strg+G zum Anzeigen)', + '{{count}} {{name}} file': '{{count}} {{name}}-Datei', + '{{count}} {{name}} files': '{{count}} {{name}}-Dateien', + '{{count}} MCP server': '{{count}} MCP-Server', + '{{count}} MCP servers': '{{count}} MCP-Server', + '{{count}} Blocked': '{{count}} blockiert', + '(ctrl+t to view)': '(Strg+T zum Anzeigen)', + '(ctrl+t to toggle)': '(Strg+T zum Umschalten)', + 'Press Ctrl+C again to exit.': 'Drucken Sie erneut Strg+C zum Beenden.', + 'Press Ctrl+D again to exit.': 'Drucken Sie erneut Strg+D zum Beenden.', + 'Press Esc again to clear.': 'Drucken Sie erneut Esc zum Loschen.', + + // ============================================================================ + // MCP Status + // ============================================================================ + 'No MCP servers configured.': 'Keine MCP-Server konfiguriert.', + 'Please view MCP documentation in your browser:': + 'Bitte sehen Sie die MCP-Dokumentation in Ihrem Browser:', + 'or use the cli /docs command': 'oder verwenden Sie den CLI-Befehl /docs', + '⏳ MCP servers are starting up ({{count}} initializing)...': + '⏳ MCP-Server werden gestartet ({{count}} werden initialisiert)...', + 'Note: First startup may take longer. Tool availability will update automatically.': + 'Hinweis: Der erste Start kann langer dauern. Werkzeugverfugbarkeit wird automatisch aktualisiert.', + 'Configured MCP servers:': 'Konfigurierte MCP-Server:', + Ready: 'Bereit', + 'Starting... (first startup may take longer)': + 'Wird gestartet... (erster Start kann langer dauern)', + Disconnected: 'Getrennt', + '{{count}} tool': '{{count}} Werkzeug', + '{{count}} tools': '{{count}} Werkzeuge', + '{{count}} prompt': '{{count}} Prompt', + '{{count}} prompts': '{{count}} Prompts', + '(from {{extensionName}})': '(von {{extensionName}})', + OAuth: 'OAuth', + 'OAuth expired': 'OAuth abgelaufen', + 'OAuth not authenticated': 'OAuth nicht authentifiziert', + 'tools and prompts will appear when ready': + 'Werkzeuge und Prompts werden angezeigt, wenn bereit', + '{{count}} tools cached': '{{count}} Werkzeuge zwischengespeichert', + 'Tools:': 'Werkzeuge:', + 'Parameters:': 'Parameter:', + 'Prompts:': 'Prompts:', + Blocked: 'Blockiert', + '💡 Tips:': '💡 Tipps:', + Use: 'Verwenden', + 'to show server and tool descriptions': + 'um Server- und Werkzeugbeschreibungen anzuzeigen', + 'to show tool parameter schemas': 'um Werkzeug-Parameter-Schemas anzuzeigen', + 'to hide descriptions': 'um Beschreibungen auszublenden', + 'to authenticate with OAuth-enabled servers': + 'um sich bei OAuth-fahigen Servern zu authentifizieren', + Press: 'Drucken Sie', + 'to toggle tool descriptions on/off': + 'um Werkzeugbeschreibungen ein-/auszuschalten', + "Starting OAuth authentication for MCP server '{{name}}'...": + "OAuth-Authentifizierung fur MCP-Server '{{name}}' wird gestartet...", + 'Restarting MCP servers...': 'MCP-Server werden neu gestartet...', + + // ============================================================================ + // Startup Tips + // ============================================================================ + 'Tips for getting started:': 'Tipps zum Einstieg:', + '1. Ask questions, edit files, or run commands.': + '1. Stellen Sie Fragen, bearbeiten Sie Dateien oder fuhren Sie Befehle aus.', + '2. Be specific for the best results.': + '2. Seien Sie spezifisch fur die besten Ergebnisse.', + 'files to customize your interactions with Qwen Code.': + 'Dateien, um Ihre Interaktionen mit Qwen Code anzupassen.', + 'for more information.': 'fur weitere Informationen.', + + // ============================================================================ + // Exit Screen / Stats + // ============================================================================ + 'Agent powering down. Goodbye!': 'Agent wird heruntergefahren. Auf Wiedersehen!', + 'To continue this session, run': 'Um diese Sitzung fortzusetzen, fuhren Sie aus', + 'Interaction Summary': 'Interaktionszusammenfassung', + 'Session ID:': 'Sitzungs-ID:', + 'Tool Calls:': 'Werkzeugaufrufe:', + 'Success Rate:': 'Erfolgsrate:', + 'User Agreement:': 'Benutzerzustimmung:', + reviewed: 'uberpruft', + 'Code Changes:': 'Codeanderungen:', + Performance: 'Leistung', + 'Wall Time:': 'Gesamtzeit:', + 'Agent Active:': 'Agent aktiv:', + 'API Time:': 'API-Zeit:', + 'Tool Time:': 'Werkzeugzeit:', + 'Session Stats': 'Sitzungsstatistiken', + 'Model Usage': 'Modellnutzung', + Reqs: 'Anfragen', + 'Input Tokens': 'Eingabe-Token', + 'Output Tokens': 'Ausgabe-Token', + 'Savings Highlight:': 'Einsparungen:', + 'of input tokens were served from the cache, reducing costs.': + 'der Eingabe-Token wurden aus dem Cache bedient, was die Kosten reduziert.', + 'Tip: For a full token breakdown, run `/stats model`.': + 'Tipp: Fur eine vollstandige Token-Aufschlusselung fuhren Sie `/stats model` aus.', + 'Model Stats For Nerds': 'Modellstatistiken fur Nerds', + 'Tool Stats For Nerds': 'Werkzeugstatistiken fur Nerds', + Metric: 'Metrik', + API: 'API', + Requests: 'Anfragen', + Errors: 'Fehler', + 'Avg Latency': 'Durchschn. Latenz', + Tokens: 'Token', + Total: 'Gesamt', + Prompt: 'Prompt', + Cached: 'Zwischengespeichert', + Thoughts: 'Gedanken', + Tool: 'Werkzeug', + Output: 'Ausgabe', + 'No API calls have been made in this session.': + 'In dieser Sitzung wurden keine API-Aufrufe gemacht.', + 'Tool Name': 'Werkzeugname', + Calls: 'Aufrufe', + 'Success Rate': 'Erfolgsrate', + 'Avg Duration': 'Durchschn. Dauer', + 'User Decision Summary': 'Benutzerentscheidungs-Zusammenfassung', + 'Total Reviewed Suggestions:': 'Insgesamt uberprufter Vorschlage:', + ' » Accepted:': ' » Akzeptiert:', + ' » Rejected:': ' » Abgelehnt:', + ' » Modified:': ' » Geandert:', + ' Overall Agreement Rate:': ' Gesamtzustimmungsrate:', + 'No tool calls have been made in this session.': + 'In dieser Sitzung wurden keine Werkzeugaufrufe gemacht.', + 'Session start time is unavailable, cannot calculate stats.': + 'Sitzungsstartzeit nicht verfugbar, Statistiken konnen nicht berechnet werden.', + + // ============================================================================ + // Loading Phrases + // ============================================================================ + 'Waiting for user confirmation...': 'Warten auf Benutzerbestatigung...', + '(esc to cancel, {{time}})': '(Esc zum Abbrechen, {{time}})', + + // ============================================================================ + // Loading Phrases + // ============================================================================ + WITTY_LOADING_PHRASES: [ + 'Auf gut Gluck!', + 'Genialitat wird ausgeliefert...', + 'Die Serifen werden aufgemalt...', + 'Durch den Schleimpilz navigieren...', + 'Die digitalen Geister werden befragt...', + 'Splines werden retikuliert...', + 'Die KI-Hamster werden aufgewarmt...', + 'Die Zaubermuschel wird befragt...', + 'Witzige Erwiderung wird generiert...', + 'Die Algorithmen werden poliert...', + 'Perfektion braucht Zeit (mein Code auch)...', + 'Frische Bytes werden gebruht...', + 'Elektronen werden gezahlt...', + 'Kognitive Prozessoren werden aktiviert...', + 'Auf Syntaxfehler im Universum wird gepruft...', + 'Einen Moment, Humor wird optimiert...', + 'Pointen werden gemischt...', + 'Neuronale Netze werden entwirrt...', + 'Brillanz wird kompiliert...', + 'wit.exe wird geladen...', + 'Die Wolke der Weisheit wird beschworen...', + 'Eine witzige Antwort wird vorbereitet...', + 'Einen Moment, ich debugge die Realitat...', + 'Die Optionen werden verwirrt...', + 'Kosmische Frequenzen werden eingestellt...', + 'Eine Antwort wird erstellt, die Ihrer Geduld wurdig ist...', + 'Die Einsen und Nullen werden kompiliert...', + 'Abhangigkeiten werden aufgelost... und existenzielle Krisen...', + 'Erinnerungen werden defragmentiert... sowohl RAM als auch personliche...', + 'Das Humor-Modul wird neu gestartet...', + 'Das Wesentliche wird zwischengespeichert (hauptsachlich Katzen-Memes)...', + 'Fur lacherliche Geschwindigkeit wird optimiert', + 'Bits werden getauscht... sagen Sie es nicht den Bytes...', + 'Garbage Collection lauft... bin gleich zuruck...', + 'Das Internet wird zusammengebaut...', + 'Kaffee wird in Code umgewandelt...', + 'Die Syntax der Realitat wird aktualisiert...', + 'Die Synapsen werden neu verdrahtet...', + 'Ein verlegtes Semikolon wird gesucht...', + 'Die Zahnrader werden geschmiert...', + 'Die Server werden vorgeheizt...', + 'Der Fluxkompensator wird kalibriert...', + 'Der Unwahrscheinlichkeitsantrieb wird aktiviert...', + 'Die Macht wird kanalisiert...', + 'Die Sterne werden fur optimale Antwort ausgerichtet...', + 'So sagen wir alle...', + 'Die nachste grosse Idee wird geladen...', + 'Einen Moment, ich bin in der Zone...', + 'Bereite mich vor, Sie mit Brillanz zu blenden...', + 'Einen Augenblick, ich poliere meinen Witz...', + 'Halten Sie durch, ich erschaffe ein Meisterwerk...', + 'Einen Moment, ich debugge das Universum...', + 'Einen Moment, ich richte die Pixel aus...', + 'Einen Moment, ich optimiere den Humor...', + 'Einen Moment, ich tune die Algorithmen...', + 'Warp-Geschwindigkeit aktiviert...', + 'Mehr Dilithium-Kristalle werden gesucht...', + 'Keine Panik...', + 'Dem weissen Kaninchen wird gefolgt...', + 'Die Wahrheit ist hier drin... irgendwo...', + 'Auf die Kassette wird gepustet...', + 'Ladevorgang... Machen Sie eine Fassrolle!', + 'Auf den Respawn wird gewartet...', + 'Der Kessel-Flug wird in weniger als 12 Parsec beendet...', + 'Der Kuchen ist keine Luge, er ladt nur noch...', + 'Am Charaktererstellungsbildschirm wird herumgefummelt...', + 'Einen Moment, ich suche das richtige Meme...', + "'A' wird zum Fortfahren gedruckt...", + 'Digitale Katzen werden gehuttert...', + 'Die Pixel werden poliert...', + 'Ein passender Ladebildschirm-Witz wird gesucht...', + 'Ich lenke Sie mit diesem witzigen Spruch ab...', + 'Fast da... wahrscheinlich...', + 'Unsere Hamster arbeiten so schnell sie konnen...', + 'Cloudy wird am Kopf gestreichelt...', + 'Die Katze wird gestreichelt...', + 'Meinen Chef rickrollen...', + 'Never gonna give you up, never gonna let you down...', + 'Auf den Bass wird geschlagen...', + 'Die Schnozbeeren werden probiert...', + "I'm going the distance, I'm going for speed...", + 'Ist dies das wahre Leben? Ist dies nur Fantasie?...', + 'Ich habe ein gutes Gefuhl dabei...', + 'Den Baren wird gestupst...', + 'Recherche zu den neuesten Memes...', + 'Uberlege, wie ich das witziger machen kann...', + 'Hmmm... lassen Sie mich nachdenken...', + 'Wie nennt man einen Fisch ohne Augen? Ein Fsh...', + 'Warum ging der Computer zur Therapie? Er hatte zu viele Bytes...', + 'Warum mogen Programmierer keine Natur? Sie hat zu viele Bugs...', + 'Warum bevorzugen Programmierer den Dunkelmodus? Weil Licht Bugs anzieht...', + 'Warum ging der Entwickler pleite? Er hat seinen ganzen Cache aufgebraucht...', + 'Was kann man mit einem kaputten Bleistift machen? Nichts, er ist sinnlos...', + 'Perkussive Wartung wird angewendet...', + 'Die richtige USB-Ausrichtung wird gesucht...', + 'Es wird sichergestellt, dass der magische Rauch in den Kabeln bleibt...', + 'Versuche Vim zu beenden...', + 'Das Hamsterrad wird angeworfen...', + 'Das ist kein Bug, das ist ein undokumentiertes Feature...', + 'Engage.', + 'Ich komme wieder... mit einer Antwort.', + 'Mein anderer Prozess ist eine TARDIS...', + 'Mit dem Maschinengeist wird kommuniziert...', + 'Die Gedanken marinieren lassen...', + 'Gerade erinnert, wo ich meine Schlussel hingelegt habe...', + 'Uber die Kugel wird nachgedacht...', + 'Ich habe Dinge gesehen, die Sie nicht glauben wurden... wie einen Benutzer, der Lademeldungen liest.', + 'Nachdenklicher Blick wird initiiert...', + 'Was ist der Lieblingssnack eines Computers? Mikrochips.', + 'Warum tragen Java-Entwickler Brillen? Weil sie nicht C#.', + 'Der Laser wird aufgeladen... pew pew!', + 'Durch Null wird geteilt... nur Spass!', + 'Suche nach einem erwachsenen Aufseh... ich meine, Verarbeitung.', + 'Es piept und boopt.', + 'Pufferung... weil auch KIs einen Moment brauchen.', + 'Quantenteilchen werden fur schnellere Antwort verschrankt...', + 'Das Chrom wird poliert... an den Algorithmen.', + 'Sind Sie nicht unterhalten? (Arbeite daran!)', + 'Die Code-Gremlins werden beschworen... zum Helfen, naturlich.', + 'Warte nur auf das Einwahlton-Ende...', + 'Das Humor-O-Meter wird neu kalibriert.', + 'Mein anderer Ladebildschirm ist noch lustiger.', + 'Ziemlich sicher, dass irgendwo eine Katze uber die Tastatur lauft...', + 'Verbessern... Verbessern... Ladt noch.', + 'Das ist kein Bug, das ist ein Feature... dieses Ladebildschirms.', + 'Haben Sie versucht, es aus- und wieder einzuschalten? (Den Ladebildschirm, nicht mich.)', + 'Zusatzliche Pylonen werden gebaut...', + ], +}; diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js index fb9475426..25fe74ece 100644 --- a/packages/cli/src/i18n/locales/en.js +++ b/packages/cli/src/i18n/locales/en.js @@ -1037,7 +1037,6 @@ export default { 'Applying percussive maintenance...', 'Searching for the correct USB orientation...', 'Ensuring the magic smoke stays inside the wires...', - 'Rewriting in Rust for no particular reason...', 'Trying to exit Vim...', 'Spinning up the hamster wheel...', "That's not a bug, it's an undocumented feature...", diff --git a/packages/cli/src/i18n/locales/ru.js b/packages/cli/src/i18n/locales/ru.js index ee583e0f9..8db55e331 100644 --- a/packages/cli/src/i18n/locales/ru.js +++ b/packages/cli/src/i18n/locales/ru.js @@ -1056,7 +1056,6 @@ export default { 'Провожу настройку методом тыка...', 'Ищем, какой стороной вставлять флешку...', 'Следим, чтобы волшебный дым не вышел из проводов...', - 'Переписываем всё на Rust без особой причины...', 'Пытаемся выйти из Vim...', 'Раскручиваем колесо для хомяка...', 'Это не баг, а фича...', From 0ae59b900c2d6b0ac118281c460df5097de9bccb Mon Sep 17 00:00:00 2001 From: Alexander Farber Date: Tue, 30 Dec 2025 16:50:23 +0100 Subject: [PATCH 30/65] Add German umlauts --- packages/cli/src/i18n/locales/de.js | 528 ++++++++++++++-------------- 1 file changed, 264 insertions(+), 264 deletions(-) diff --git a/packages/cli/src/i18n/locales/de.js b/packages/cli/src/i18n/locales/de.js index 4ddcf4c3d..832dd1333 100644 --- a/packages/cli/src/i18n/locales/de.js +++ b/packages/cli/src/i18n/locales/de.js @@ -5,26 +5,26 @@ */ // German translations for Qwen Code CLI -// Deutsche Ubersetzungen fur Qwen Code CLI +// Deutsche Übersetzungen für Qwen Code CLI export default { // ============================================================================ // Help / UI Components // ============================================================================ 'Basics:': 'Grundlagen:', - 'Add context': 'Kontext hinzufugen', + 'Add context': 'Kontext hinzufügen', 'Use {{symbol}} to specify files for context (e.g., {{example}}) to target specific files or folders.': - 'Verwenden Sie {{symbol}}, um Dateien als Kontext anzugeben (z.B. {{example}}), um bestimmte Dateien oder Ordner auszuwahlen.', + 'Verwenden Sie {{symbol}}, um Dateien als Kontext anzugeben (z.B. {{example}}), um bestimmte Dateien oder Ordner auszuwählen.', '@': '@', '@src/myFile.ts': '@src/myFile.ts', 'Shell mode': 'Shell-Modus', 'YOLO mode': 'YOLO-Modus', 'plan mode': 'Planungsmodus', - 'auto-accept edits': 'Anderungen automatisch akzeptieren', - 'Accepting edits': 'Anderungen werden akzeptiert', + 'auto-accept edits': 'Änderungen automatisch akzeptieren', + 'Accepting edits': 'Änderungen werden akzeptiert', '(shift + tab to cycle)': '(Umschalt + Tab zum Wechseln)', 'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).': - 'Shell-Befehle uber {{symbol}} ausfuhren (z.B. {{example1}}) oder naturliche Sprache verwenden (z.B. {{example2}}).', + 'Shell-Befehle über {{symbol}} ausführen (z.B. {{example1}}) oder natürliche Sprache verwenden (z.B. {{example2}}).', '!': '!', '!npm run start': '!npm run start', 'start server': 'Server starten', @@ -32,33 +32,33 @@ export default { 'shell command': 'Shell-Befehl', 'Model Context Protocol command (from external servers)': 'Model Context Protocol Befehl (von externen Servern)', - 'Keyboard Shortcuts:': 'Tastenkurzel:', - 'Jump through words in the input': 'Worter in der Eingabe uberspringen', + 'Keyboard Shortcuts:': 'Tastenkürzel:', + 'Jump through words in the input': 'Wörter in der Eingabe überspringen', 'Close dialogs, cancel requests, or quit application': - 'Dialoge schliessen, Anfragen abbrechen oder Anwendung beenden', + 'Dialoge schließen, Anfragen abbrechen oder Anwendung beenden', 'New line': 'Neue Zeile', 'New line (Alt+Enter works for certain linux distros)': 'Neue Zeile (Alt+Enter funktioniert bei bestimmten Linux-Distributionen)', - 'Clear the screen': 'Bildschirm loschen', - 'Open input in external editor': 'Eingabe in externem Editor offnen', + 'Clear the screen': 'Bildschirm löschen', + 'Open input in external editor': 'Eingabe in externem Editor öffnen', 'Send message': 'Nachricht senden', 'Initializing...': 'Initialisierung...', 'Connecting to MCP servers... ({{connected}}/{{total}})': 'Verbindung zu MCP-Servern wird hergestellt... ({{connected}}/{{total}})', 'Type your message or @path/to/file': 'Nachricht eingeben oder @Pfad/zur/Datei', "Press 'i' for INSERT mode and 'Esc' for NORMAL mode.": - "Drucken Sie 'i' fur den EINFUGE-Modus und 'Esc' fur den NORMAL-Modus.", + "Drücken Sie 'i' für den EINFÜGE-Modus und 'Esc' für den NORMAL-Modus.", 'Cancel operation / Clear input (double press)': - 'Vorgang abbrechen / Eingabe loschen (doppelt drucken)', + 'Vorgang abbrechen / Eingabe löschen (doppelt drücken)', 'Cycle approval modes': 'Genehmigungsmodi durchschalten', - 'Cycle through your prompt history': 'Eingabeverlauf durchblattern', + 'Cycle through your prompt history': 'Eingabeverlauf durchblättern', 'For a full list of shortcuts, see {{docPath}}': - 'Eine vollstandige Liste der Tastenkurzel finden Sie unter {{docPath}}', + 'Eine vollständige Liste der Tastenkürzel finden Sie unter {{docPath}}', 'docs/keyboard-shortcuts.md': 'docs/keyboard-shortcuts.md', - 'for help on Qwen Code': 'fur Hilfe zu Qwen Code', + 'for help on Qwen Code': 'für Hilfe zu Qwen Code', 'show version info': 'Versionsinformationen anzeigen', 'submit a bug report': 'Fehlerbericht einreichen', - 'About Qwen Code': 'Uber Qwen Code', + 'About Qwen Code': 'Über Qwen Code', // ============================================================================ // System Information Fields @@ -82,34 +82,34 @@ export default { // Commands - General // ============================================================================ 'Analyzes the project and creates a tailored QWEN.md file.': - 'Analysiert das Projekt und erstellt eine massgeschneiderte QWEN.md-Datei.', + 'Analysiert das Projekt und erstellt eine maßgeschneiderte QWEN.md-Datei.', 'list available Qwen Code tools. Usage: /tools [desc]': - 'Verfugbare Qwen Code Werkzeuge auflisten. Verwendung: /tools [desc]', - 'Available Qwen Code CLI tools:': 'Verfugbare Qwen Code CLI-Werkzeuge:', - 'No tools available': 'Keine Werkzeuge verfugbar', + 'Verfügbare Qwen Code Werkzeuge auflisten. Verwendung: /tools [desc]', + 'Available Qwen Code CLI tools:': 'Verfügbare Qwen Code CLI-Werkzeuge:', + 'No tools available': 'Keine Werkzeuge verfügbar', 'View or change the approval mode for tool usage': - 'Genehmigungsmodus fur Werkzeugnutzung anzeigen oder andern', - 'View or change the language setting': 'Spracheinstellung anzeigen oder andern', - 'change the theme': 'Design andern', - 'Select Theme': 'Design auswahlen', + 'Genehmigungsmodus für Werkzeugnutzung anzeigen oder ändern', + 'View or change the language setting': 'Spracheinstellung anzeigen oder ändern', + 'change the theme': 'Design ändern', + 'Select Theme': 'Design auswählen', Preview: 'Vorschau', '(Use Enter to select, Tab to configure scope)': - '(Enter zum Auswahlen, Tab zum Konfigurieren des Bereichs)', + '(Enter zum Auswählen, Tab zum Konfigurieren des Bereichs)', '(Use Enter to apply scope, Tab to select theme)': - '(Enter zum Anwenden des Bereichs, Tab zum Auswahlen des Designs)', + '(Enter zum Anwenden des Bereichs, Tab zum Auswählen des Designs)', 'Theme configuration unavailable due to NO_COLOR env variable.': - 'Design-Konfiguration aufgrund der NO_COLOR-Umgebungsvariable nicht verfugbar.', + 'Design-Konfiguration aufgrund der NO_COLOR-Umgebungsvariable nicht verfügbar.', 'Theme "{{themeName}}" not found.': 'Design "{{themeName}}" nicht gefunden.', 'Theme "{{themeName}}" not found in selected scope.': - 'Design "{{themeName}}" im ausgewahlten Bereich nicht gefunden.', + 'Design "{{themeName}}" im ausgewählten Bereich nicht gefunden.', 'Clear conversation history and free up context': - 'Gesprachsverlauf loschen und Kontext freigeben', + 'Gesprächsverlauf löschen und Kontext freigeben', 'Compresses the context by replacing it with a summary.': 'Komprimiert den Kontext durch Ersetzen mit einer Zusammenfassung.', 'open full Qwen Code documentation in your browser': - 'Vollstandige Qwen Code Dokumentation im Browser offnen', - 'Configuration not available.': 'Konfiguration nicht verfugbar.', - 'change the auth method': 'Authentifizierungsmethode andern', + 'Vollständige Qwen Code Dokumentation im Browser öffnen', + 'Configuration not available.': 'Konfiguration nicht verfügbar.', + 'change the auth method': 'Authentifizierungsmethode ändern', 'Copy the last result or code snippet to clipboard': 'Letztes Ergebnis oder Codeausschnitt in die Zwischenablage kopieren', @@ -117,55 +117,55 @@ export default { // Commands - Agents // ============================================================================ 'Manage subagents for specialized task delegation.': - 'Unteragenten fur spezialisierte Aufgabendelegation verwalten.', + 'Unteragenten für spezialisierte Aufgabendelegation verwalten.', 'Manage existing subagents (view, edit, delete).': - 'Bestehende Unteragenten verwalten (anzeigen, bearbeiten, loschen).', + 'Bestehende Unteragenten verwalten (anzeigen, bearbeiten, löschen).', 'Create a new subagent with guided setup.': - 'Neuen Unteragenten mit gefuhrter Einrichtung erstellen.', + 'Neuen Unteragenten mit geführter Einrichtung erstellen.', // ============================================================================ // Agents - Management Dialog // ============================================================================ Agents: 'Agenten', - 'Choose Action': 'Aktion wahlen', + 'Choose Action': 'Aktion wählen', 'Edit {{name}}': '{{name}} bearbeiten', 'Edit Tools: {{name}}': 'Werkzeuge bearbeiten: {{name}}', 'Edit Color: {{name}}': 'Farbe bearbeiten: {{name}}', - 'Delete {{name}}': '{{name}} loschen', + 'Delete {{name}}': '{{name}} löschen', 'Unknown Step': 'Unbekannter Schritt', - 'Esc to close': 'Esc zum Schliessen', + 'Esc to close': 'Esc zum Schließen', 'Enter to select, ↑↓ to navigate, Esc to close': - 'Enter zum Auswahlen, ↑↓ zum Navigieren, Esc zum Schliessen', - 'Esc to go back': 'Esc zum Zuruckgehen', - 'Enter to confirm, Esc to cancel': 'Enter zum Bestatigen, Esc zum Abbrechen', + 'Enter zum Auswählen, ↑↓ zum Navigieren, Esc zum Schließen', + 'Esc to go back': 'Esc zum Zurückgehen', + 'Enter to confirm, Esc to cancel': 'Enter zum Bestätigen, Esc zum Abbrechen', 'Enter to select, ↑↓ to navigate, Esc to go back': - 'Enter zum Auswahlen, ↑↓ zum Navigieren, Esc zum Zuruckgehen', - 'Invalid step: {{step}}': 'Ungultiger Schritt: {{step}}', + 'Enter zum Auswählen, ↑↓ zum Navigieren, Esc zum Zurückgehen', + 'Invalid step: {{step}}': 'Ungültiger Schritt: {{step}}', 'No subagents found.': 'Keine Unteragenten gefunden.', "Use '/agents create' to create your first subagent.": "Verwenden Sie '/agents create', um Ihren ersten Unteragenten zu erstellen.", '(built-in)': '(integriert)', - '(overridden by project level agent)': '(uberschrieben durch Projektagent)', + '(overridden by project level agent)': '(überschrieben durch Projektagent)', 'Project Level ({{path}})': 'Projektebene ({{path}})', 'User Level ({{path}})': 'Benutzerebene ({{path}})', 'Built-in Agents': 'Integrierte Agenten', 'Using: {{count}} agents': 'Verwendet: {{count}} Agenten', 'View Agent': 'Agent anzeigen', 'Edit Agent': 'Agent bearbeiten', - 'Delete Agent': 'Agent loschen', - Back: 'Zuruck', - 'No agent selected': 'Kein Agent ausgewahlt', + 'Delete Agent': 'Agent löschen', + Back: 'Zurück', + 'No agent selected': 'Kein Agent ausgewählt', 'File Path: ': 'Dateipfad: ', 'Tools: ': 'Werkzeuge: ', 'Color: ': 'Farbe: ', 'Description:': 'Beschreibung:', 'System Prompt:': 'System-Prompt:', - 'Open in editor': 'Im Editor offnen', + 'Open in editor': 'Im Editor öffnen', 'Edit tools': 'Werkzeuge bearbeiten', 'Edit color': 'Farbe bearbeiten', '❌ Error:': '❌ Fehler:', 'Are you sure you want to delete agent "{{name}}"?': - 'Sind Sie sicher, dass Sie den Agenten "{{name}}" loschen mochten?', + 'Sind Sie sicher, dass Sie den Agenten "{{name}}" löschen möchten?', // ============================================================================ // Agents - Creation Wizard // ============================================================================ @@ -179,26 +179,26 @@ export default { '❌ Error saving subagent:': '❌ Fehler beim Speichern des Unteragenten:', 'Warnings:': 'Warnungen:', 'Name "{{name}}" already exists at {{level}} level - will overwrite existing subagent': - 'Name "{{name}}" existiert bereits auf {{level}}-Ebene - bestehender Unteragent wird uberschrieben', + 'Name "{{name}}" existiert bereits auf {{level}}-Ebene - bestehender Unteragent wird überschrieben', 'Name "{{name}}" exists at user level - project level will take precedence': 'Name "{{name}}" existiert auf Benutzerebene - Projektebene hat Vorrang', 'Name "{{name}}" exists at project level - existing subagent will take precedence': 'Name "{{name}}" existiert auf Projektebene - bestehender Unteragent hat Vorrang', 'Description is over {{length}} characters': - 'Beschreibung ist uber {{length}} Zeichen', + 'Beschreibung ist über {{length}} Zeichen', 'System prompt is over {{length}} characters': - 'System-Prompt ist uber {{length}} Zeichen', + 'System-Prompt ist über {{length}} Zeichen', // Agents - Creation Wizard Steps - 'Step {{n}}: Choose Location': 'Schritt {{n}}: Speicherort wahlen', + 'Step {{n}}: Choose Location': 'Schritt {{n}}: Speicherort wählen', 'Step {{n}}: Choose Generation Method': - 'Schritt {{n}}: Generierungsmethode wahlen', + 'Schritt {{n}}: Generierungsmethode wählen', 'Generate with Qwen Code (Recommended)': 'Mit Qwen Code generieren (Empfohlen)', 'Manual Creation': 'Manuelle Erstellung', 'Describe what this subagent should do and when it should be used. (Be comprehensive for best results)': - 'Beschreiben Sie, was dieser Unteragent tun soll und wann er verwendet werden soll. (Ausfuhrliche Beschreibung fur beste Ergebnisse)', + 'Beschreiben Sie, was dieser Unteragent tun soll und wann er verwendet werden soll. (Ausführliche Beschreibung für beste Ergebnisse)', 'e.g., Expert code reviewer that reviews code based on best practices...': - 'z.B. Experte fur Code-Reviews, der Code nach Best Practices uberpruft...', + 'z.B. Experte für Code-Reviews, der Code nach Best Practices überprüft...', 'Generating subagent configuration...': 'Unteragent-Konfiguration wird generiert...', 'Failed to generate subagent: {{error}}': @@ -208,42 +208,42 @@ export default { 'Step {{n}}: Enter System Prompt': 'Schritt {{n}}: System-Prompt eingeben', 'Step {{n}}: Enter Description': 'Schritt {{n}}: Beschreibung eingeben', // Agents - Tool Selection - 'Step {{n}}: Select Tools': 'Schritt {{n}}: Werkzeuge auswahlen', + 'Step {{n}}: Select Tools': 'Schritt {{n}}: Werkzeuge auswählen', 'All Tools (Default)': 'Alle Werkzeuge (Standard)', 'All Tools': 'Alle Werkzeuge', 'Read-only Tools': 'Nur-Lese-Werkzeuge', 'Read & Edit Tools': 'Lese- und Bearbeitungswerkzeuge', - 'Read & Edit & Execution Tools': 'Lese-, Bearbeitungs- und Ausfuhrungswerkzeuge', + 'Read & Edit & Execution Tools': 'Lese-, Bearbeitungs- und Ausführungswerkzeuge', 'All tools selected, including MCP tools': - 'Alle Werkzeuge ausgewahlt, einschliesslich MCP-Werkzeuge', - 'Selected tools:': 'Ausgewahlte Werkzeuge:', + 'Alle Werkzeuge ausgewählt, einschließlich MCP-Werkzeuge', + 'Selected tools:': 'Ausgewählte Werkzeuge:', 'Read-only tools:': 'Nur-Lese-Werkzeuge:', 'Edit tools:': 'Bearbeitungswerkzeuge:', - 'Execution tools:': 'Ausfuhrungswerkzeuge:', - 'Step {{n}}: Choose Background Color': 'Schritt {{n}}: Hintergrundfarbe wahlen', - 'Step {{n}}: Confirm and Save': 'Schritt {{n}}: Bestatigen und Speichern', + 'Execution tools:': 'Ausführungswerkzeuge:', + 'Step {{n}}: Choose Background Color': 'Schritt {{n}}: Hintergrundfarbe wählen', + 'Step {{n}}: Confirm and Save': 'Schritt {{n}}: Bestätigen und Speichern', // Agents - Navigation & Instructions 'Esc to cancel': 'Esc zum Abbrechen', 'Press Enter to save, e to save and edit, Esc to go back': - 'Enter zum Speichern, e zum Speichern und Bearbeiten, Esc zum Zuruckgehen', + 'Enter zum Speichern, e zum Speichern und Bearbeiten, Esc zum Zurückgehen', 'Press Enter to continue, {{navigation}}Esc to {{action}}': 'Enter zum Fortfahren, {{navigation}}Esc zum {{action}}', cancel: 'Abbrechen', - 'go back': 'Zuruckgehen', + 'go back': 'Zurückgehen', '↑↓ to navigate, ': '↑↓ zum Navigieren, ', 'Enter a clear, unique name for this subagent.': - 'Geben Sie einen eindeutigen Namen fur diesen Unteragenten ein.', + 'Geben Sie einen eindeutigen Namen für diesen Unteragenten ein.', 'e.g., Code Reviewer': 'z.B. Code-Reviewer', 'Name cannot be empty.': 'Name darf nicht leer sein.', "Write the system prompt that defines this subagent's behavior. Be comprehensive for best results.": - 'Schreiben Sie den System-Prompt, der das Verhalten dieses Unteragenten definiert. Ausfuhrlich fur beste Ergebnisse.', + 'Schreiben Sie den System-Prompt, der das Verhalten dieses Unteragenten definiert. Ausführlich für beste Ergebnisse.', 'e.g., You are an expert code reviewer...': - 'z.B. Sie sind ein Experte fur Code-Reviews...', + 'z.B. Sie sind ein Experte für Code-Reviews...', 'System prompt cannot be empty.': 'System-Prompt darf nicht leer sein.', 'Describe when and how this subagent should be used.': 'Beschreiben Sie, wann und wie dieser Unteragent verwendet werden soll.', 'e.g., Reviews code for best practices and potential bugs.': - 'z.B. Uberpruft Code auf Best Practices und mogliche Fehler.', + 'z.B. Überprüft Code auf Best Practices und mögliche Fehler.', 'Description cannot be empty.': 'Beschreibung darf nicht leer sein.', 'Failed to launch editor: {{error}}': 'Fehler beim Starten des Editors: {{error}}', 'Failed to save and edit subagent: {{error}}': @@ -254,18 +254,18 @@ export default { // ============================================================================ 'View and edit Qwen Code settings': 'Qwen Code Einstellungen anzeigen und bearbeiten', Settings: 'Einstellungen', - '(Use Enter to select{{tabText}})': '(Enter zum Auswahlen{{tabText}})', + '(Use Enter to select{{tabText}})': '(Enter zum Auswählen{{tabText}})', ', Tab to change focus': ', Tab zum Fokuswechsel', 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.': - 'Um Anderungen zu sehen, muss Qwen Code neu gestartet werden. Drucken Sie r, um jetzt zu beenden und Anderungen anzuwenden.', + 'Um Änderungen zu sehen, muss Qwen Code neu gestartet werden. Drücken Sie r, um jetzt zu beenden und Änderungen anzuwenden.', 'The command "/{{command}}" is not supported in non-interactive mode.': - 'Der Befehl "/{{command}}" wird im nicht-interaktiven Modus nicht unterstutzt.', + 'Der Befehl "/{{command}}" wird im nicht-interaktiven Modus nicht unterstützt.', // ============================================================================ // Settings Labels // ============================================================================ 'Vim Mode': 'Vim-Modus', 'Disable Auto Update': 'Automatische Updates deaktivieren', - 'Enable Prompt Completion': 'Eingabevervollstandigung aktivieren', + 'Enable Prompt Completion': 'Eingabevervollständigung aktivieren', 'Debug Keystroke Logging': 'Debug-Protokollierung von Tastatureingaben', Language: 'Sprache', 'Output Format': 'Ausgabeformat', @@ -277,25 +277,25 @@ export default { 'Hide CWD': 'Arbeitsverzeichnis ausblenden', 'Hide Sandbox Status': 'Sandbox-Status ausblenden', 'Hide Model Info': 'Modellinformationen ausblenden', - 'Hide Footer': 'Fusszeile ausblenden', + 'Hide Footer': 'Fußzeile ausblenden', 'Show Memory Usage': 'Speichernutzung anzeigen', 'Show Line Numbers': 'Zeilennummern anzeigen', 'Show Citations': 'Quellenangaben anzeigen', - 'Custom Witty Phrases': 'Benutzerdefinierte Witzige Spruche', - 'Enable Welcome Back': 'Willkommen-zuruck aktivieren', - 'Disable Loading Phrases': 'Ladespruche deaktivieren', + 'Custom Witty Phrases': 'Benutzerdefinierte Witzige Sprüche', + 'Enable Welcome Back': 'Willkommen-zurück aktivieren', + 'Disable Loading Phrases': 'Ladesprüche deaktivieren', 'Screen Reader Mode': 'Bildschirmleser-Modus', 'IDE Mode': 'IDE-Modus', 'Max Session Turns': 'Maximale Sitzungsrunden', - 'Skip Next Speaker Check': 'Nachste-Sprecher-Prufung uberspringen', - 'Skip Loop Detection': 'Schleifenerkennung uberspringen', - 'Skip Startup Context': 'Startkontext uberspringen', + 'Skip Next Speaker Check': 'Nächste-Sprecher-Prüfung überspringen', + 'Skip Loop Detection': 'Schleifenerkennung überspringen', + 'Skip Startup Context': 'Startkontext überspringen', 'Enable OpenAI Logging': 'OpenAI-Protokollierung aktivieren', 'OpenAI Logging Directory': 'OpenAI-Protokollierungsverzeichnis', Timeout: 'Zeitlimit', 'Max Retries': 'Maximale Wiederholungen', 'Disable Cache Control': 'Cache-Steuerung deaktivieren', - 'Memory Discovery Max Dirs': 'Maximale Verzeichnisse fur Speichererkennung', + 'Memory Discovery Max Dirs': 'Maximale Verzeichnisse für Speichererkennung', 'Load Memory From Include Directories': 'Speicher aus Include-Verzeichnissen laden', 'Respect .gitignore': '.gitignore beachten', @@ -307,12 +307,12 @@ export default { 'Auto Accept': 'Automatisch akzeptieren', 'Use Ripgrep': 'Ripgrep verwenden', 'Use Builtin Ripgrep': 'Integriertes Ripgrep verwenden', - 'Enable Tool Output Truncation': 'Werkzeugausgabe-Kurzung aktivieren', - 'Tool Output Truncation Threshold': 'Schwellenwert fur Werkzeugausgabe-Kurzung', - 'Tool Output Truncation Lines': 'Zeilen fur Werkzeugausgabe-Kurzung', + 'Enable Tool Output Truncation': 'Werkzeugausgabe-Kürzung aktivieren', + 'Tool Output Truncation Threshold': 'Schwellenwert für Werkzeugausgabe-Kürzung', + 'Tool Output Truncation Lines': 'Zeilen für Werkzeugausgabe-Kürzung', 'Folder Trust': 'Ordnervertrauen', 'Vision Model Preview': 'Vision-Modell-Vorschau', - 'Tool Schema Compliance': 'Werkzeug-Schema-Konformitat', + 'Tool Schema Compliance': 'Werkzeug-Schema-Konformität', // Settings enum options 'Auto (detect from system)': 'Automatisch (vom System erkennen)', Text: 'Text', @@ -323,17 +323,17 @@ export default { YOLO: 'YOLO', 'toggle vim mode on/off': 'Vim-Modus ein-/ausschalten', 'check session stats. Usage: /stats [model|tools]': - 'Sitzungsstatistiken prufen. Verwendung: /stats [model|tools]', + 'Sitzungsstatistiken prüfen. Verwendung: /stats [model|tools]', 'Show model-specific usage statistics.': 'Modellspezifische Nutzungsstatistiken anzeigen.', 'Show tool-specific usage statistics.': 'Werkzeugspezifische Nutzungsstatistiken anzeigen.', 'exit the cli': 'CLI beenden', 'list configured MCP servers and tools, or authenticate with OAuth-enabled servers': - 'Konfigurierte MCP-Server und Werkzeuge auflisten oder mit OAuth-fahigen Servern authentifizieren', + 'Konfigurierte MCP-Server und Werkzeuge auflisten oder mit OAuth-fähigen Servern authentifizieren', 'Manage workspace directories': 'Arbeitsbereichsverzeichnisse verwalten', 'Add directories to the workspace. Use comma to separate multiple paths': - 'Verzeichnisse zum Arbeitsbereich hinzufugen. Komma zum Trennen mehrerer Pfade verwenden', + 'Verzeichnisse zum Arbeitsbereich hinzufügen. Komma zum Trennen mehrerer Pfade verwenden', 'Show all directories in the workspace': 'Alle Verzeichnisse im Arbeitsbereich anzeigen', 'set external editor preference': 'Externen Editor festlegen', @@ -342,55 +342,55 @@ export default { 'Update extensions. Usage: update |--all': 'Erweiterungen aktualisieren. Verwendung: update |--all', 'manage IDE integration': 'IDE-Integration verwalten', - 'check status of IDE integration': 'Status der IDE-Integration prufen', + 'check status of IDE integration': 'Status der IDE-Integration prüfen', 'install required IDE companion for {{ideName}}': - 'Erforderlichen IDE-Begleiter fur {{ideName}} installieren', + 'Erforderlichen IDE-Begleiter für {{ideName}} installieren', 'enable IDE integration': 'IDE-Integration aktivieren', 'disable IDE integration': 'IDE-Integration deaktivieren', 'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.': - 'IDE-Integration wird in Ihrer aktuellen Umgebung nicht unterstutzt. Um diese Funktion zu nutzen, fuhren Sie Qwen Code in einer dieser unterstutzten IDEs aus: VS Code oder VS Code-Forks.', + 'IDE-Integration wird in Ihrer aktuellen Umgebung nicht unterstützt. Um diese Funktion zu nutzen, führen Sie Qwen Code in einer dieser unterstützten IDEs aus: VS Code oder VS Code-Forks.', 'Set up GitHub Actions': 'GitHub Actions einrichten', 'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf, Trae)': - 'Terminal-Tastenbelegungen fur mehrzeilige Eingabe konfigurieren (VS Code, Cursor, Windsurf, Trae)', + 'Terminal-Tastenbelegungen für mehrzeilige Eingabe konfigurieren (VS Code, Cursor, Windsurf, Trae)', 'Please restart your terminal for the changes to take effect.': - 'Bitte starten Sie Ihr Terminal neu, damit die Anderungen wirksam werden.', + 'Bitte starten Sie Ihr Terminal neu, damit die Änderungen wirksam werden.', 'Failed to configure terminal: {{error}}': 'Fehler beim Konfigurieren des Terminals: {{error}}', 'Could not determine {{terminalName}} config path on Windows: APPDATA environment variable is not set.': 'Konnte {{terminalName}}-Konfigurationspfad unter Windows nicht ermitteln: APPDATA-Umgebungsvariable ist nicht gesetzt.', '{{terminalName}} keybindings.json exists but is not a valid JSON array. Please fix the file manually or delete it to allow automatic configuration.': - '{{terminalName}} keybindings.json existiert, ist aber kein gultiges JSON-Array. Bitte korrigieren Sie die Datei manuell oder loschen Sie sie, um automatische Konfiguration zu ermoglichen.', + '{{terminalName}} keybindings.json existiert, ist aber kein gültiges JSON-Array. Bitte korrigieren Sie die Datei manuell oder löschen Sie sie, um automatische Konfiguration zu ermöglichen.', 'File: {{file}}': 'Datei: {{file}}', 'Failed to parse {{terminalName}} keybindings.json. The file contains invalid JSON. Please fix the file manually or delete it to allow automatic configuration.': - 'Fehler beim Parsen von {{terminalName}} keybindings.json. Die Datei enthalt ungultiges JSON. Bitte korrigieren Sie die Datei manuell oder loschen Sie sie, um automatische Konfiguration zu ermoglichen.', + 'Fehler beim Parsen von {{terminalName}} keybindings.json. Die Datei enthält ungültiges JSON. Bitte korrigieren Sie die Datei manuell oder löschen Sie sie, um automatische Konfiguration zu ermöglichen.', 'Error: {{error}}': 'Fehler: {{error}}', 'Shift+Enter binding already exists': 'Umschalt+Enter-Belegung existiert bereits', 'Ctrl+Enter binding already exists': 'Strg+Enter-Belegung existiert bereits', 'Existing keybindings detected. Will not modify to avoid conflicts.': - 'Bestehende Tastenbelegungen erkannt. Keine Anderungen, um Konflikte zu vermeiden.', + 'Bestehende Tastenbelegungen erkannt. Keine Änderungen, um Konflikte zu vermeiden.', 'Please check and modify manually if needed: {{file}}': - 'Bitte prufen und bei Bedarf manuell andern: {{file}}', + 'Bitte prüfen und bei Bedarf manuell ändern: {{file}}', 'Added Shift+Enter and Ctrl+Enter keybindings to {{terminalName}}.': - 'Umschalt+Enter und Strg+Enter Tastenbelegungen zu {{terminalName}} hinzugefugt.', - 'Modified: {{file}}': 'Geandert: {{file}}', + 'Umschalt+Enter und Strg+Enter Tastenbelegungen zu {{terminalName}} hinzugefügt.', + 'Modified: {{file}}': 'Geändert: {{file}}', '{{terminalName}} keybindings already configured.': '{{terminalName}}-Tastenbelegungen bereits konfiguriert.', 'Failed to configure {{terminalName}}.': 'Fehler beim Konfigurieren von {{terminalName}}.', 'Your terminal is already configured for an optimal experience with multiline input (Shift+Enter and Ctrl+Enter).': - 'Ihr Terminal ist bereits fur optimale Erfahrung mit mehrzeiliger Eingabe konfiguriert (Umschalt+Enter und Strg+Enter).', + 'Ihr Terminal ist bereits für optimale Erfahrung mit mehrzeiliger Eingabe konfiguriert (Umschalt+Enter und Strg+Enter).', 'Could not detect terminal type. Supported terminals: VS Code, Cursor, Windsurf, and Trae.': - 'Terminal-Typ konnte nicht erkannt werden. Unterstutzte Terminals: VS Code, Cursor, Windsurf und Trae.', + 'Terminal-Typ konnte nicht erkannt werden. Unterstützte Terminals: VS Code, Cursor, Windsurf und Trae.', 'Terminal "{{terminal}}" is not supported yet.': - 'Terminal "{{terminal}}" wird noch nicht unterstutzt.', + 'Terminal "{{terminal}}" wird noch nicht unterstützt.', // ============================================================================ // Commands - Language // ============================================================================ 'Invalid language. Available: en-US, zh-CN': - 'Ungultige Sprache. Verfugbar: en-US, zh-CN', + 'Ungültige Sprache. Verfügbar: en-US, zh-CN', 'Language subcommands do not accept additional arguments.': - 'Sprach-Unterbefehle akzeptieren keine zusatzlichen Argumente.', + 'Sprach-Unterbefehle akzeptieren keine zusätzlichen Argumente.', 'Current UI language: {{lang}}': 'Aktuelle UI-Sprache: {{lang}}', 'Current LLM output language: {{lang}}': 'Aktuelle LLM-Ausgabesprache: {{lang}}', @@ -402,19 +402,19 @@ export default { 'Example: /language output 中文': 'Beispiel: /language output Deutsch', 'Example: /language output English': 'Beispiel: /language output English', 'Example: /language output 日本語': 'Beispiel: /language output Japanisch', - 'UI language changed to {{lang}}': 'UI-Sprache geandert zu {{lang}}', + 'UI language changed to {{lang}}': 'UI-Sprache geändert zu {{lang}}', 'LLM output language rule file generated at {{path}}': 'LLM-Ausgabesprach-Regeldatei generiert unter {{path}}', 'Please restart the application for the changes to take effect.': - 'Bitte starten Sie die Anwendung neu, damit die Anderungen wirksam werden.', + 'Bitte starten Sie die Anwendung neu, damit die Änderungen wirksam werden.', 'Failed to generate LLM output language rule file: {{error}}': 'Fehler beim Generieren der LLM-Ausgabesprach-Regeldatei: {{error}}', 'Invalid command. Available subcommands:': - 'Ungultiger Befehl. Verfugbare Unterbefehle:', - 'Available subcommands:': 'Verfugbare Unterbefehle:', + 'Ungültiger Befehl. Verfügbare Unterbefehle:', + 'Available subcommands:': 'Verfügbare Unterbefehle:', 'To request additional UI language packs, please open an issue on GitHub.': - 'Um zusatzliche UI-Sprachpakete anzufordern, offnen Sie bitte ein Issue auf GitHub.', - 'Available options:': 'Verfugbare Optionen:', + 'Um zusätzliche UI-Sprachpakete anzufordern, öffnen Sie bitte ein Issue auf GitHub.', + 'Available options:': 'Verfügbare Optionen:', ' - zh-CN: Simplified Chinese': ' - zh-CN: Vereinfachtes Chinesisch', ' - en-US: English': ' - en-US: Englisch', 'Set UI language to Simplified Chinese (zh-CN)': @@ -426,45 +426,45 @@ export default { // ============================================================================ 'Approval Mode': 'Genehmigungsmodus', 'Current approval mode: {{mode}}': 'Aktueller Genehmigungsmodus: {{mode}}', - 'Available approval modes:': 'Verfugbare Genehmigungsmodi:', - 'Approval mode changed to: {{mode}}': 'Genehmigungsmodus geandert zu: {{mode}}', + 'Available approval modes:': 'Verfügbare Genehmigungsmodi:', + 'Approval mode changed to: {{mode}}': 'Genehmigungsmodus geändert zu: {{mode}}', 'Approval mode changed to: {{mode}} (saved to {{scope}} settings{{location}})': - 'Genehmigungsmodus geandert zu: {{mode}} (gespeichert in {{scope}} Einstellungen{{location}})', + 'Genehmigungsmodus geändert zu: {{mode}} (gespeichert in {{scope}} Einstellungen{{location}})', 'Usage: /approval-mode [--session|--user|--project]': 'Verwendung: /approval-mode [--session|--user|--project]', 'Scope subcommands do not accept additional arguments.': - 'Bereichs-Unterbefehle akzeptieren keine zusatzlichen Argumente.', + 'Bereichs-Unterbefehle akzeptieren keine zusätzlichen Argumente.', 'Plan mode - Analyze only, do not modify files or execute commands': - 'Planungsmodus - Nur analysieren, keine Dateien andern oder Befehle ausfuhren', + 'Planungsmodus - Nur analysieren, keine Dateien ändern oder Befehle ausführen', 'Default mode - Require approval for file edits or shell commands': - 'Standardmodus - Genehmigung fur Dateibearbeitungen oder Shell-Befehle erforderlich', + 'Standardmodus - Genehmigung für Dateibearbeitungen oder Shell-Befehle erforderlich', 'Auto-edit mode - Automatically approve file edits': 'Automatischer Bearbeitungsmodus - Dateibearbeitungen automatisch genehmigen', 'YOLO mode - Automatically approve all tools': 'YOLO-Modus - Alle Werkzeuge automatisch genehmigen', '{{mode}} mode': '{{mode}}-Modus', 'Settings service is not available; unable to persist the approval mode.': - 'Einstellungsdienst nicht verfugbar; Genehmigungsmodus kann nicht gespeichert werden.', + 'Einstellungsdienst nicht verfügbar; Genehmigungsmodus kann nicht gespeichert werden.', 'Failed to save approval mode: {{error}}': 'Fehler beim Speichern des Genehmigungsmodus: {{error}}', 'Failed to change approval mode: {{error}}': - 'Fehler beim Andern des Genehmigungsmodus: {{error}}', + 'Fehler beim Ändern des Genehmigungsmodus: {{error}}', 'Apply to current session only (temporary)': - 'Nur auf aktuelle Sitzung anwenden (temporar)', - 'Persist for this project/workspace': 'Fur dieses Projekt/Arbeitsbereich speichern', + 'Nur auf aktuelle Sitzung anwenden (temporär)', + 'Persist for this project/workspace': 'Für dieses Projekt/Arbeitsbereich speichern', 'Persist for this user on this machine': - 'Fur diesen Benutzer auf diesem Computer speichern', + 'Für diesen Benutzer auf diesem Computer speichern', 'Analyze only, do not modify files or execute commands': - 'Nur analysieren, keine Dateien andern oder Befehle ausfuhren', + 'Nur analysieren, keine Dateien ändern oder Befehle ausführen', 'Require approval for file edits or shell commands': - 'Genehmigung fur Dateibearbeitungen oder Shell-Befehle erforderlich', + 'Genehmigung für Dateibearbeitungen oder Shell-Befehle erforderlich', 'Automatically approve file edits': 'Dateibearbeitungen automatisch genehmigen', 'Automatically approve all tools': 'Alle Werkzeuge automatisch genehmigen', 'Workspace approval mode exists and takes priority. User-level change will have no effect.': - 'Arbeitsbereich-Genehmigungsmodus existiert und hat Vorrang. Benutzerebene-Anderung hat keine Wirkung.', + 'Arbeitsbereich-Genehmigungsmodus existiert und hat Vorrang. Benutzerebene-Änderung hat keine Wirkung.', '(Use Enter to select, Tab to change focus)': - '(Enter zum Auswahlen, Tab zum Fokuswechsel)', + '(Enter zum Auswählen, Tab zum Fokuswechsel)', 'Apply To': 'Anwenden auf', 'User Settings': 'Benutzereinstellungen', 'Workspace Settings': 'Arbeitsbereich-Einstellungen', @@ -473,13 +473,13 @@ export default { // Commands - Memory // ============================================================================ 'Commands for interacting with memory.': - 'Befehle fur die Interaktion mit dem Speicher.', + 'Befehle für die Interaktion mit dem Speicher.', 'Show the current memory contents.': 'Aktuellen Speicherinhalt anzeigen.', 'Show project-level memory contents.': 'Projektebene-Speicherinhalt anzeigen.', 'Show global memory contents.': 'Globalen Speicherinhalt anzeigen.', 'Add content to project-level memory.': - 'Inhalt zum Projektebene-Speicher hinzufugen.', - 'Add content to global memory.': 'Inhalt zum globalen Speicher hinzufugen.', + 'Inhalt zum Projektebene-Speicher hinzufügen.', + 'Add content to global memory.': 'Inhalt zum globalen Speicher hinzufügen.', 'Refresh the memory from the source.': 'Speicher aus der Quelle aktualisieren.', 'Usage: /memory add --project ': 'Verwendung: /memory add --project ', @@ -505,7 +505,7 @@ export default { 'Refreshing memory from source files...': 'Speicher wird aus Quelldateien aktualisiert...', 'Add content to the memory. Use --global for global memory or --project for project memory.': - 'Inhalt zum Speicher hinzufugen. --global fur globalen Speicher oder --project fur Projektspeicher verwenden.', + 'Inhalt zum Speicher hinzufügen. --global für globalen Speicher oder --project für Projektspeicher verwenden.', 'Usage: /memory add [--global|--project] ': 'Verwendung: /memory add [--global|--project] ', 'Attempting to save to memory {{scope}}: "{{fact}}"': @@ -515,7 +515,7 @@ export default { // Commands - MCP // ============================================================================ 'Authenticate with an OAuth-enabled MCP server': - 'Mit einem OAuth-fahigen MCP-Server authentifizieren', + 'Mit einem OAuth-fähigen MCP-Server authentifizieren', 'List configured MCP servers and tools': 'Konfigurierte MCP-Server und Werkzeuge auflisten', 'Restarts MCP servers.': 'MCP-Server neu starten.', @@ -529,7 +529,7 @@ export default { 'Verwenden Sie /mcp auth zur Authentifizierung.', "MCP server '{{name}}' not found.": "MCP-Server '{{name}}' nicht gefunden.", "Successfully authenticated and refreshed tools for '{{name}}'.": - "Erfolgreich authentifiziert und Werkzeuge fur '{{name}}' aktualisiert.", + "Erfolgreich authentifiziert und Werkzeuge für '{{name}}' aktualisiert.", "Failed to authenticate with MCP server '{{name}}': {{error}}": "Authentifizierung mit MCP-Server '{{name}}' fehlgeschlagen: {{error}}", "Re-discovering tools from '{{name}}'...": @@ -538,47 +538,47 @@ export default { // ============================================================================ // Commands - Chat // ============================================================================ - 'Manage conversation history.': 'Gesprachsverlauf verwalten.', - 'List saved conversation checkpoints': 'Gespeicherte Gesprachspruefpunkte auflisten', + 'Manage conversation history.': 'Gesprächsverlauf verwalten.', + 'List saved conversation checkpoints': 'Gespeicherte Gesprächsprüfpunkte auflisten', 'No saved conversation checkpoints found.': - 'Keine gespeicherten Gesprachsprufpunkte gefunden.', - 'List of saved conversations:': 'Liste gespeicherter Gesprache:', - 'Note: Newest last, oldest first': 'Hinweis: Neueste zuletzt, alteste zuerst', + 'Keine gespeicherten Gesprächsprüfpunkte gefunden.', + 'List of saved conversations:': 'Liste gespeicherter Gespräche:', + 'Note: Newest last, oldest first': 'Hinweis: Neueste zuletzt, älteste zuerst', 'Save the current conversation as a checkpoint. Usage: /chat save ': - 'Aktuelles Gesprach als Prufpunkt speichern. Verwendung: /chat save ', + 'Aktuelles Gespräch als Prüfpunkt speichern. Verwendung: /chat save ', 'Missing tag. Usage: /chat save ': 'Tag fehlt. Verwendung: /chat save ', 'Delete a conversation checkpoint. Usage: /chat delete ': - 'Gesprachsprufpunkt loschen. Verwendung: /chat delete ', + 'Gesprächsprüfpunkt löschen. Verwendung: /chat delete ', 'Missing tag. Usage: /chat delete ': 'Tag fehlt. Verwendung: /chat delete ', "Conversation checkpoint '{{tag}}' has been deleted.": - "Gesprachsprufpunkt '{{tag}}' wurde geloscht.", + "Gesprächsprüfpunkt '{{tag}}' wurde gelöscht.", "Error: No checkpoint found with tag '{{tag}}'.": - "Fehler: Kein Prufpunkt mit Tag '{{tag}}' gefunden.", + "Fehler: Kein Prüfpunkt mit Tag '{{tag}}' gefunden.", 'Resume a conversation from a checkpoint. Usage: /chat resume ': - 'Gesprach von einem Prufpunkt fortsetzen. Verwendung: /chat resume ', + 'Gespräch von einem Prüfpunkt fortsetzen. Verwendung: /chat resume ', 'Missing tag. Usage: /chat resume ': 'Tag fehlt. Verwendung: /chat resume ', 'No saved checkpoint found with tag: {{tag}}.': - 'Kein gespeicherter Prufpunkt mit Tag gefunden: {{tag}}.', + 'Kein gespeicherter Prüfpunkt mit Tag gefunden: {{tag}}.', 'A checkpoint with the tag {{tag}} already exists. Do you want to overwrite it?': - 'Ein Prufpunkt mit dem Tag {{tag}} existiert bereits. Mochten Sie ihn uberschreiben?', + 'Ein Prüfpunkt mit dem Tag {{tag}} existiert bereits. Möchten Sie ihn überschreiben?', 'No chat client available to save conversation.': - 'Kein Chat-Client verfugbar, um Gesprach zu speichern.', + 'Kein Chat-Client verfügbar, um Gespräch zu speichern.', 'Conversation checkpoint saved with tag: {{tag}}.': - 'Gesprachsprufpunkt gespeichert mit Tag: {{tag}}.', - 'No conversation found to save.': 'Kein Gesprach zum Speichern gefunden.', + 'Gesprächsprüfpunkt gespeichert mit Tag: {{tag}}.', + 'No conversation found to save.': 'Kein Gespräch zum Speichern gefunden.', 'No chat client available to share conversation.': - 'Kein Chat-Client verfugbar, um Gesprach zu teilen.', + 'Kein Chat-Client verfügbar, um Gespräch zu teilen.', 'Invalid file format. Only .md and .json are supported.': - 'Ungultiges Dateiformat. Nur .md und .json werden unterstutzt.', + 'Ungültiges Dateiformat. Nur .md und .json werden unterstützt.', 'Error sharing conversation: {{error}}': - 'Fehler beim Teilen des Gesprachs: {{error}}', - 'Conversation shared to {{filePath}}': 'Gesprach geteilt nach {{filePath}}', - 'No conversation found to share.': 'Kein Gesprach zum Teilen gefunden.', + 'Fehler beim Teilen des Gesprächs: {{error}}', + 'Conversation shared to {{filePath}}': 'Gespräch geteilt nach {{filePath}}', + 'No conversation found to share.': 'Kein Gespräch zum Teilen gefunden.', 'Share the current conversation to a markdown or json file. Usage: /chat share ': - 'Aktuelles Gesprach in eine Markdown- oder JSON-Datei teilen. Verwendung: /chat share ', + 'Aktuelles Gespräch in eine Markdown- oder JSON-Datei teilen. Verwendung: /chat share ', // ============================================================================ // Commands - Summary @@ -586,10 +586,10 @@ export default { 'Generate a project summary and save it to .qwen/PROJECT_SUMMARY.md': 'Projektzusammenfassung generieren und in .qwen/PROJECT_SUMMARY.md speichern', 'No chat client available to generate summary.': - 'Kein Chat-Client verfugbar, um Zusammenfassung zu generieren.', + 'Kein Chat-Client verfügbar, um Zusammenfassung zu generieren.', 'Already generating summary, wait for previous request to complete': 'Zusammenfassung wird bereits generiert, warten Sie auf Abschluss der vorherigen Anfrage', - 'No conversation found to summarize.': 'Kein Gesprach zum Zusammenfassen gefunden.', + 'No conversation found to summarize.': 'Kein Gespräch zum Zusammenfassen gefunden.', 'Failed to generate project context summary: {{error}}': 'Fehler beim Generieren der Projektkontextzusammenfassung: {{error}}', 'Saved project summary to {{filePathForDisplay}}.': @@ -602,26 +602,26 @@ export default { // ============================================================================ // Commands - Model // ============================================================================ - 'Switch the model for this session': 'Modell fur diese Sitzung wechseln', + 'Switch the model for this session': 'Modell für diese Sitzung wechseln', 'Content generator configuration not available.': - 'Inhaltsgenerator-Konfiguration nicht verfugbar.', - 'Authentication type not available.': 'Authentifizierungstyp nicht verfugbar.', + 'Inhaltsgenerator-Konfiguration nicht verfügbar.', + 'Authentication type not available.': 'Authentifizierungstyp nicht verfügbar.', 'No models available for the current authentication type ({{authType}}).': - 'Keine Modelle fur den aktuellen Authentifizierungstyp ({{authType}}) verfugbar.', + 'Keine Modelle für den aktuellen Authentifizierungstyp ({{authType}}) verfügbar.', // ============================================================================ // Commands - Clear // ============================================================================ 'Starting a new session, resetting chat, and clearing terminal.': - 'Neue Sitzung wird gestartet, Chat wird zuruckgesetzt und Terminal wird geloscht.', + 'Neue Sitzung wird gestartet, Chat wird zurückgesetzt und Terminal wird gelöscht.', 'Starting a new session and clearing.': - 'Neue Sitzung wird gestartet und geloscht.', + 'Neue Sitzung wird gestartet und gelöscht.', // ============================================================================ // Commands - Compress // ============================================================================ 'Already compressing, wait for previous request to complete': - 'Komprimierung lauft bereits, warten Sie auf Abschluss der vorherigen Anfrage', + 'Komprimierung läuft bereits, warten Sie auf Abschluss der vorherigen Anfrage', 'Failed to compress chat history.': 'Fehler beim Komprimieren des Chatverlaufs.', 'Failed to compress chat history: {{error}}': 'Fehler beim Komprimieren des Chatverlaufs: {{error}}', @@ -629,27 +629,27 @@ export default { 'Chat history compressed from {{originalTokens}} to {{newTokens}} tokens.': 'Chatverlauf komprimiert von {{originalTokens}} auf {{newTokens}} Token.', 'Compression was not beneficial for this history size.': - 'Komprimierung war fur diese Verlaufsgross nicht vorteilhaft.', + 'Komprimierung war für diese Verlaufsgröße nicht vorteilhaft.', 'Chat history compression did not reduce size. This may indicate issues with the compression prompt.': - 'Chatverlauf-Komprimierung hat die Grosse nicht reduziert. Dies kann auf Probleme mit dem Komprimierungs-Prompt hindeuten.', + 'Chatverlauf-Komprimierung hat die Größe nicht reduziert. Dies kann auf Probleme mit dem Komprimierungs-Prompt hindeuten.', 'Could not compress chat history due to a token counting error.': - 'Chatverlauf konnte aufgrund eines Token-Zahlfehlers nicht komprimiert werden.', + 'Chatverlauf konnte aufgrund eines Token-Zählfehlers nicht komprimiert werden.', 'Chat history is already compressed.': 'Chatverlauf ist bereits komprimiert.', // ============================================================================ // Commands - Directory // ============================================================================ - 'Configuration is not available.': 'Konfiguration ist nicht verfugbar.', + 'Configuration is not available.': 'Konfiguration ist nicht verfügbar.', 'Please provide at least one path to add.': - 'Bitte geben Sie mindestens einen Pfad zum Hinzufugen an.', + 'Bitte geben Sie mindestens einen Pfad zum Hinzufügen an.', 'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.': - 'Der Befehl /directory add wird in restriktiven Sandbox-Profilen nicht unterstutzt. Bitte verwenden Sie --include-directories beim Starten der Sitzung.', - "Error adding '{{path}}': {{error}}": "Fehler beim Hinzufugen von '{{path}}': {{error}}", + 'Der Befehl /directory add wird in restriktiven Sandbox-Profilen nicht unterstützt. Bitte verwenden Sie --include-directories beim Starten der Sitzung.', + "Error adding '{{path}}': {{error}}": "Fehler beim Hinzufügen von '{{path}}': {{error}}", 'Successfully added QWEN.md files from the following directories if there are:\n- {{directories}}': - 'QWEN.md-Dateien aus folgenden Verzeichnissen erfolgreich hinzugefugt, falls vorhanden:\n- {{directories}}', + 'QWEN.md-Dateien aus folgenden Verzeichnissen erfolgreich hinzugefügt, falls vorhanden:\n- {{directories}}', 'Error refreshing memory: {{error}}': 'Fehler beim Aktualisieren des Speichers: {{error}}', 'Successfully added directories:\n- {{directories}}': - 'Verzeichnisse erfolgreich hinzugefugt:\n- {{directories}}', + 'Verzeichnisse erfolgreich hinzugefügt:\n- {{directories}}', 'Current workspace directories:\n{{directories}}': 'Aktuelle Arbeitsbereichsverzeichnisse:\n{{directories}}', @@ -657,36 +657,36 @@ export default { // Commands - Docs // ============================================================================ 'Please open the following URL in your browser to view the documentation:\n{{url}}': - 'Bitte offnen Sie folgende URL in Ihrem Browser, um die Dokumentation anzusehen:\n{{url}}', + 'Bitte öffnen Sie folgende URL in Ihrem Browser, um die Dokumentation anzusehen:\n{{url}}', 'Opening documentation in your browser: {{url}}': - 'Dokumentation wird in Ihrem Browser geoffnet: {{url}}', + 'Dokumentation wird in Ihrem Browser geöffnet: {{url}}', // ============================================================================ // Dialogs - Tool Confirmation // ============================================================================ - 'Do you want to proceed?': 'Mochten Sie fortfahren?', + 'Do you want to proceed?': 'Möchten Sie fortfahren?', 'Yes, allow once': 'Ja, einmal erlauben', 'Allow always': 'Immer erlauben', No: 'Nein', 'No (esc)': 'Nein (Esc)', - 'Yes, allow always for this session': 'Ja, fur diese Sitzung immer erlauben', - 'Modify in progress:': 'Anderung in Bearbeitung:', + 'Yes, allow always for this session': 'Ja, für diese Sitzung immer erlauben', + 'Modify in progress:': 'Änderung in Bearbeitung:', 'Save and close external editor to continue': - 'Speichern und externen Editor schliessen, um fortzufahren', - 'Apply this change?': 'Diese Anderung anwenden?', + 'Speichern und externen Editor schließen, um fortzufahren', + 'Apply this change?': 'Diese Änderung anwenden?', 'Yes, allow always': 'Ja, immer erlauben', 'Modify with external editor': 'Mit externem Editor bearbeiten', - 'No, suggest changes (esc)': 'Nein, Anderungen vorschlagen (Esc)', - "Allow execution of: '{{command}}'?": "Ausfuhrung erlauben von: '{{command}}'?", + 'No, suggest changes (esc)': 'Nein, Änderungen vorschlagen (Esc)', + "Allow execution of: '{{command}}'?": "Ausführung erlauben von: '{{command}}'?", 'Yes, allow always ...': 'Ja, immer erlauben ...', - 'Yes, and auto-accept edits': 'Ja, und Anderungen automatisch akzeptieren', - 'Yes, and manually approve edits': 'Ja, und Anderungen manuell genehmigen', + 'Yes, and auto-accept edits': 'Ja, und Änderungen automatisch akzeptieren', + 'Yes, and manually approve edits': 'Ja, und Änderungen manuell genehmigen', 'No, keep planning (esc)': 'Nein, weiter planen (Esc)', 'URLs to fetch:': 'Abzurufende URLs:', 'MCP Server: {{server}}': 'MCP-Server: {{server}}', 'Tool: {{tool}}': 'Werkzeug: {{tool}}', 'Allow execution of MCP tool "{{tool}}" from server "{{server}}"?': - 'Ausfuhrung des MCP-Werkzeugs "{{tool}}" von Server "{{server}}" erlauben?', + 'Ausführung des MCP-Werkzeugs "{{tool}}" von Server "{{server}}" erlauben?', 'Yes, always allow tool "{{tool}}" from server "{{server}}"': 'Ja, Werkzeug "{{tool}}" von Server "{{server}}" immer erlauben', 'Yes, always allow all tools from server "{{server}}"': @@ -695,17 +695,17 @@ export default { // ============================================================================ // Dialogs - Shell Confirmation // ============================================================================ - 'Shell Command Execution': 'Shell-Befehlsausfuhrung', + 'Shell Command Execution': 'Shell-Befehlsausführung', 'A custom command wants to run the following shell commands:': - 'Ein benutzerdefinierter Befehl mochte folgende Shell-Befehle ausfuhren:', + 'Ein benutzerdefinierter Befehl möchte folgende Shell-Befehle ausführen:', // ============================================================================ // Dialogs - Pro Quota // ============================================================================ 'Pro quota limit reached for {{model}}.': - 'Pro-Kontingentlimit fur {{model}} erreicht.', + 'Pro-Kontingentlimit für {{model}} erreicht.', 'Change auth (executes the /auth command)': - 'Authentifizierung andern (fuhrt den /auth-Befehl aus)', + 'Authentifizierung ändern (führt den /auth-Befehl aus)', 'Continue with {{model}}': 'Mit {{model}} fortfahren', // ============================================================================ @@ -716,13 +716,13 @@ export default { 'Fortschritt: {{done}}/{{total}} Aufgaben abgeschlossen', ', {{inProgress}} in progress': ', {{inProgress}} in Bearbeitung', 'Pending Tasks:': 'Ausstehende Aufgaben:', - 'What would you like to do?': 'Was mochten Sie tun?', + 'What would you like to do?': 'Was möchten Sie tun?', 'Choose how to proceed with your session:': - 'Wahlen Sie, wie Sie mit Ihrer Sitzung fortfahren mochten:', + 'Wählen Sie, wie Sie mit Ihrer Sitzung fortfahren möchten:', 'Start new chat session': 'Neue Chat-Sitzung starten', - 'Continue previous conversation': 'Vorheriges Gesprach fortsetzen', + 'Continue previous conversation': 'Vorheriges Gespräch fortsetzen', '👋 Welcome back! (Last updated: {{timeAgo}})': - '👋 Willkommen zuruck! (Zuletzt aktualisiert: {{timeAgo}})', + '👋 Willkommen zurück! (Zuletzt aktualisiert: {{timeAgo}})', '🎯 Overall Goal:': '🎯 Gesamtziel:', // ============================================================================ @@ -730,14 +730,14 @@ export default { // ============================================================================ 'Get started': 'Loslegen', 'How would you like to authenticate for this project?': - 'Wie mochten Sie sich fur dieses Projekt authentifizieren?', + 'Wie möchten Sie sich für dieses Projekt authentifizieren?', 'OpenAI API key is required to use OpenAI authentication.': - 'OpenAI API-Schlussel ist fur die OpenAI-Authentifizierung erforderlich.', + 'OpenAI API-Schlüssel ist für die OpenAI-Authentifizierung erforderlich.', 'You must select an auth method to proceed. Press Ctrl+C again to exit.': - 'Sie mussen eine Authentifizierungsmethode wahlen, um fortzufahren. Drucken Sie erneut Strg+C zum Beenden.', + 'Sie müssen eine Authentifizierungsmethode wählen, um fortzufahren. Drücken Sie erneut Strg+C zum Beenden.', '(Use Enter to Set Auth)': '(Enter zum Festlegen der Authentifizierung)', 'Terms of Services and Privacy Notice for Qwen Code': - 'Nutzungsbedingungen und Datenschutzhinweis fur Qwen Code', + 'Nutzungsbedingungen und Datenschutzhinweis für Qwen Code', 'Qwen OAuth': 'Qwen OAuth', OpenAI: 'OpenAI', 'Failed to login. Message: {{message}}': @@ -753,32 +753,32 @@ export default { 'Or scan the QR code below:': 'Oder scannen Sie den QR-Code unten:', 'Waiting for authorization': 'Warten auf Autorisierung', 'Time remaining:': 'Verbleibende Zeit:', - '(Press ESC or CTRL+C to cancel)': '(ESC oder STRG+C zum Abbrechen drucken)', + '(Press ESC or CTRL+C to cancel)': '(ESC oder STRG+C zum Abbrechen drücken)', 'Qwen OAuth Authentication Timeout': 'Qwen OAuth-Authentifizierung abgelaufen', 'OAuth token expired (over {{seconds}} seconds). Please select authentication method again.': - 'OAuth-Token abgelaufen (uber {{seconds}} Sekunden). Bitte wahlen Sie erneut eine Authentifizierungsmethode.', + 'OAuth-Token abgelaufen (über {{seconds}} Sekunden). Bitte wählen Sie erneut eine Authentifizierungsmethode.', 'Press any key to return to authentication type selection.': - 'Drucken Sie eine beliebige Taste, um zur Authentifizierungstypauswahl zuruckzukehren.', + 'Drücken Sie eine beliebige Taste, um zur Authentifizierungstypauswahl zurückzukehren.', 'Waiting for Qwen OAuth authentication...': 'Warten auf Qwen OAuth-Authentifizierung...', 'Note: Your existing API key in settings.json will not be cleared when using Qwen OAuth. You can switch back to OpenAI authentication later if needed.': - 'Hinweis: Ihr bestehender API-Schlussel in settings.json wird bei Verwendung von Qwen OAuth nicht geloscht. Sie konnen spater bei Bedarf zur OpenAI-Authentifizierung zuruckwechseln.', + 'Hinweis: Ihr bestehender API-Schlüssel in settings.json wird bei Verwendung von Qwen OAuth nicht gelöscht. Sie können später bei Bedarf zur OpenAI-Authentifizierung zurückwechseln.', 'Authentication timed out. Please try again.': 'Authentifizierung abgelaufen. Bitte versuchen Sie es erneut.', 'Waiting for auth... (Press ESC or CTRL+C to cancel)': - 'Warten auf Authentifizierung... (ESC oder STRG+C zum Abbrechen drucken)', + 'Warten auf Authentifizierung... (ESC oder STRG+C zum Abbrechen drücken)', 'Failed to authenticate. Message: {{message}}': 'Authentifizierung fehlgeschlagen. Meldung: {{message}}', 'Authenticated successfully with {{authType}} credentials.': 'Erfolgreich mit {{authType}}-Anmeldedaten authentifiziert.', 'Invalid QWEN_DEFAULT_AUTH_TYPE value: "{{value}}". Valid values are: {{validValues}}': - 'Ungultiger QWEN_DEFAULT_AUTH_TYPE-Wert: "{{value}}". Gultige Werte sind: {{validValues}}', + 'Ungültiger QWEN_DEFAULT_AUTH_TYPE-Wert: "{{value}}". Gültige Werte sind: {{validValues}}', 'OpenAI Configuration Required': 'OpenAI-Konfiguration erforderlich', 'Please enter your OpenAI configuration. You can get an API key from': - 'Bitte geben Sie Ihre OpenAI-Konfiguration ein. Sie konnen einen API-Schlussel erhalten von', - 'API Key:': 'API-Schlussel:', + 'Bitte geben Sie Ihre OpenAI-Konfiguration ein. Sie können einen API-Schlüssel erhalten von', + 'API Key:': 'API-Schlüssel:', 'Invalid credentials: {{errorMessage}}': - 'Ungultige Anmeldedaten: {{errorMessage}}', + 'Ungültige Anmeldedaten: {{errorMessage}}', 'Failed to validate credentials': 'Anmeldedaten konnten nicht validiert werden', 'Press Enter to continue, Tab/↑↓ to navigate, Esc to cancel': 'Enter zum Fortfahren, Tab/↑↓ zum Navigieren, Esc zum Abbrechen', @@ -786,8 +786,8 @@ export default { // ============================================================================ // Dialogs - Model // ============================================================================ - 'Select Model': 'Modell auswahlen', - '(Press Esc to close)': '(Esc zum Schliessen drucken)', + 'Select Model': 'Modell auswählen', + '(Press Esc to close)': '(Esc zum Schließen drücken)', 'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)': 'Das neueste Qwen Coder Modell von Alibaba Cloud ModelStudio (Version: qwen3-coder-plus-2025-09-23)', 'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)': @@ -802,8 +802,8 @@ export default { // Status Bar // ============================================================================ 'Using:': 'Verwendet:', - '{{count}} open file': '{{count}} geoffnete Datei', - '{{count}} open files': '{{count}} geoffnete Dateien', + '{{count}} open file': '{{count}} geöffnete Datei', + '{{count}} open files': '{{count}} geöffnete Dateien', '(ctrl+g to view)': '(Strg+G zum Anzeigen)', '{{count}} {{name}} file': '{{count}} {{name}}-Datei', '{{count}} {{name}} files': '{{count}} {{name}}-Dateien', @@ -812,9 +812,9 @@ export default { '{{count}} Blocked': '{{count}} blockiert', '(ctrl+t to view)': '(Strg+T zum Anzeigen)', '(ctrl+t to toggle)': '(Strg+T zum Umschalten)', - 'Press Ctrl+C again to exit.': 'Drucken Sie erneut Strg+C zum Beenden.', - 'Press Ctrl+D again to exit.': 'Drucken Sie erneut Strg+D zum Beenden.', - 'Press Esc again to clear.': 'Drucken Sie erneut Esc zum Loschen.', + 'Press Ctrl+C again to exit.': 'Drücken Sie erneut Strg+C zum Beenden.', + 'Press Ctrl+D again to exit.': 'Drücken Sie erneut Strg+D zum Beenden.', + 'Press Esc again to clear.': 'Drücken Sie erneut Esc zum Löschen.', // ============================================================================ // MCP Status @@ -826,11 +826,11 @@ export default { '⏳ MCP servers are starting up ({{count}} initializing)...': '⏳ MCP-Server werden gestartet ({{count}} werden initialisiert)...', 'Note: First startup may take longer. Tool availability will update automatically.': - 'Hinweis: Der erste Start kann langer dauern. Werkzeugverfugbarkeit wird automatisch aktualisiert.', + 'Hinweis: Der erste Start kann länger dauern. Werkzeugverfügbarkeit wird automatisch aktualisiert.', 'Configured MCP servers:': 'Konfigurierte MCP-Server:', Ready: 'Bereit', 'Starting... (first startup may take longer)': - 'Wird gestartet... (erster Start kann langer dauern)', + 'Wird gestartet... (erster Start kann länger dauern)', Disconnected: 'Getrennt', '{{count}} tool': '{{count}} Werkzeug', '{{count}} tools': '{{count}} Werkzeuge', @@ -854,12 +854,12 @@ export default { 'to show tool parameter schemas': 'um Werkzeug-Parameter-Schemas anzuzeigen', 'to hide descriptions': 'um Beschreibungen auszublenden', 'to authenticate with OAuth-enabled servers': - 'um sich bei OAuth-fahigen Servern zu authentifizieren', - Press: 'Drucken Sie', + 'um sich bei OAuth-fähigen Servern zu authentifizieren', + Press: 'Drücken Sie', 'to toggle tool descriptions on/off': 'um Werkzeugbeschreibungen ein-/auszuschalten', "Starting OAuth authentication for MCP server '{{name}}'...": - "OAuth-Authentifizierung fur MCP-Server '{{name}}' wird gestartet...", + "OAuth-Authentifizierung für MCP-Server '{{name}}' wird gestartet...", 'Restarting MCP servers...': 'MCP-Server werden neu gestartet...', // ============================================================================ @@ -867,25 +867,25 @@ export default { // ============================================================================ 'Tips for getting started:': 'Tipps zum Einstieg:', '1. Ask questions, edit files, or run commands.': - '1. Stellen Sie Fragen, bearbeiten Sie Dateien oder fuhren Sie Befehle aus.', + '1. Stellen Sie Fragen, bearbeiten Sie Dateien oder führen Sie Befehle aus.', '2. Be specific for the best results.': - '2. Seien Sie spezifisch fur die besten Ergebnisse.', + '2. Seien Sie spezifisch für die besten Ergebnisse.', 'files to customize your interactions with Qwen Code.': 'Dateien, um Ihre Interaktionen mit Qwen Code anzupassen.', - 'for more information.': 'fur weitere Informationen.', + 'for more information.': 'für weitere Informationen.', // ============================================================================ // Exit Screen / Stats // ============================================================================ 'Agent powering down. Goodbye!': 'Agent wird heruntergefahren. Auf Wiedersehen!', - 'To continue this session, run': 'Um diese Sitzung fortzusetzen, fuhren Sie aus', + 'To continue this session, run': 'Um diese Sitzung fortzusetzen, führen Sie aus', 'Interaction Summary': 'Interaktionszusammenfassung', 'Session ID:': 'Sitzungs-ID:', 'Tool Calls:': 'Werkzeugaufrufe:', 'Success Rate:': 'Erfolgsrate:', 'User Agreement:': 'Benutzerzustimmung:', - reviewed: 'uberpruft', - 'Code Changes:': 'Codeanderungen:', + reviewed: 'überprüft', + 'Code Changes:': 'Codeänderungen:', Performance: 'Leistung', 'Wall Time:': 'Gesamtzeit:', 'Agent Active:': 'Agent aktiv:', @@ -900,9 +900,9 @@ export default { 'of input tokens were served from the cache, reducing costs.': 'der Eingabe-Token wurden aus dem Cache bedient, was die Kosten reduziert.', 'Tip: For a full token breakdown, run `/stats model`.': - 'Tipp: Fur eine vollstandige Token-Aufschlusselung fuhren Sie `/stats model` aus.', - 'Model Stats For Nerds': 'Modellstatistiken fur Nerds', - 'Tool Stats For Nerds': 'Werkzeugstatistiken fur Nerds', + 'Tipp: Für eine vollständige Token-Aufschlüsselung führen Sie `/stats model` aus.', + 'Model Stats For Nerds': 'Modellstatistiken für Nerds', + 'Tool Stats For Nerds': 'Werkzeugstatistiken für Nerds', Metric: 'Metrik', API: 'API', Requests: 'Anfragen', @@ -922,41 +922,41 @@ export default { 'Success Rate': 'Erfolgsrate', 'Avg Duration': 'Durchschn. Dauer', 'User Decision Summary': 'Benutzerentscheidungs-Zusammenfassung', - 'Total Reviewed Suggestions:': 'Insgesamt uberprufter Vorschlage:', + 'Total Reviewed Suggestions:': 'Insgesamt überprüfter Vorschläge:', ' » Accepted:': ' » Akzeptiert:', ' » Rejected:': ' » Abgelehnt:', - ' » Modified:': ' » Geandert:', + ' » Modified:': ' » Geändert:', ' Overall Agreement Rate:': ' Gesamtzustimmungsrate:', 'No tool calls have been made in this session.': 'In dieser Sitzung wurden keine Werkzeugaufrufe gemacht.', 'Session start time is unavailable, cannot calculate stats.': - 'Sitzungsstartzeit nicht verfugbar, Statistiken konnen nicht berechnet werden.', + 'Sitzungsstartzeit nicht verfügbar, Statistiken können nicht berechnet werden.', // ============================================================================ // Loading Phrases // ============================================================================ - 'Waiting for user confirmation...': 'Warten auf Benutzerbestatigung...', + 'Waiting for user confirmation...': 'Warten auf Benutzerbestätigung...', '(esc to cancel, {{time}})': '(Esc zum Abbrechen, {{time}})', // ============================================================================ // Loading Phrases // ============================================================================ WITTY_LOADING_PHRASES: [ - 'Auf gut Gluck!', - 'Genialitat wird ausgeliefert...', + 'Auf gut Glück!', + 'Genialität wird ausgeliefert...', 'Die Serifen werden aufgemalt...', 'Durch den Schleimpilz navigieren...', 'Die digitalen Geister werden befragt...', 'Splines werden retikuliert...', - 'Die KI-Hamster werden aufgewarmt...', + 'Die KI-Hamster werden aufgewärmt...', 'Die Zaubermuschel wird befragt...', 'Witzige Erwiderung wird generiert...', 'Die Algorithmen werden poliert...', 'Perfektion braucht Zeit (mein Code auch)...', - 'Frische Bytes werden gebruht...', - 'Elektronen werden gezahlt...', + 'Frische Bytes werden gebrüht...', + 'Elektronen werden gezählt...', 'Kognitive Prozessoren werden aktiviert...', - 'Auf Syntaxfehler im Universum wird gepruft...', + 'Auf Syntaxfehler im Universum wird geprüft...', 'Einen Moment, Humor wird optimiert...', 'Pointen werden gemischt...', 'Neuronale Netze werden entwirrt...', @@ -964,31 +964,31 @@ export default { 'wit.exe wird geladen...', 'Die Wolke der Weisheit wird beschworen...', 'Eine witzige Antwort wird vorbereitet...', - 'Einen Moment, ich debugge die Realitat...', + 'Einen Moment, ich debugge die Realität...', 'Die Optionen werden verwirrt...', 'Kosmische Frequenzen werden eingestellt...', - 'Eine Antwort wird erstellt, die Ihrer Geduld wurdig ist...', + 'Eine Antwort wird erstellt, die Ihrer Geduld würdig ist...', 'Die Einsen und Nullen werden kompiliert...', - 'Abhangigkeiten werden aufgelost... und existenzielle Krisen...', - 'Erinnerungen werden defragmentiert... sowohl RAM als auch personliche...', + 'Abhängigkeiten werden aufgelöst... und existenzielle Krisen...', + 'Erinnerungen werden defragmentiert... sowohl RAM als auch persönliche...', 'Das Humor-Modul wird neu gestartet...', - 'Das Wesentliche wird zwischengespeichert (hauptsachlich Katzen-Memes)...', - 'Fur lacherliche Geschwindigkeit wird optimiert', + 'Das Wesentliche wird zwischengespeichert (hauptsächlich Katzen-Memes)...', + 'Für lächerliche Geschwindigkeit wird optimiert', 'Bits werden getauscht... sagen Sie es nicht den Bytes...', - 'Garbage Collection lauft... bin gleich zuruck...', + 'Garbage Collection läuft... bin gleich zurück...', 'Das Internet wird zusammengebaut...', 'Kaffee wird in Code umgewandelt...', - 'Die Syntax der Realitat wird aktualisiert...', + 'Die Syntax der Realität wird aktualisiert...', 'Die Synapsen werden neu verdrahtet...', 'Ein verlegtes Semikolon wird gesucht...', - 'Die Zahnrader werden geschmiert...', + 'Die Zahnräder werden geschmiert...', 'Die Server werden vorgeheizt...', 'Der Fluxkompensator wird kalibriert...', 'Der Unwahrscheinlichkeitsantrieb wird aktiviert...', 'Die Macht wird kanalisiert...', - 'Die Sterne werden fur optimale Antwort ausgerichtet...', + 'Die Sterne werden für optimale Antwort ausgerichtet...', 'So sagen wir alle...', - 'Die nachste grosse Idee wird geladen...', + 'Die nächste große Idee wird geladen...', 'Einen Moment, ich bin in der Zone...', 'Bereite mich vor, Sie mit Brillanz zu blenden...', 'Einen Augenblick, ich poliere meinen Witz...', @@ -1000,22 +1000,22 @@ export default { 'Warp-Geschwindigkeit aktiviert...', 'Mehr Dilithium-Kristalle werden gesucht...', 'Keine Panik...', - 'Dem weissen Kaninchen wird gefolgt...', + 'Dem weißen Kaninchen wird gefolgt...', 'Die Wahrheit ist hier drin... irgendwo...', 'Auf die Kassette wird gepustet...', 'Ladevorgang... Machen Sie eine Fassrolle!', 'Auf den Respawn wird gewartet...', 'Der Kessel-Flug wird in weniger als 12 Parsec beendet...', - 'Der Kuchen ist keine Luge, er ladt nur noch...', + 'Der Kuchen ist keine Lüge, er lädt nur noch...', 'Am Charaktererstellungsbildschirm wird herumgefummelt...', 'Einen Moment, ich suche das richtige Meme...', - "'A' wird zum Fortfahren gedruckt...", - 'Digitale Katzen werden gehuttert...', + "'A' wird zum Fortfahren gedrückt...", + 'Digitale Katzen werden gehütet...', 'Die Pixel werden poliert...', 'Ein passender Ladebildschirm-Witz wird gesucht...', 'Ich lenke Sie mit diesem witzigen Spruch ab...', 'Fast da... wahrscheinlich...', - 'Unsere Hamster arbeiten so schnell sie konnen...', + 'Unsere Hamster arbeiten so schnell sie können...', 'Cloudy wird am Kopf gestreichelt...', 'Die Katze wird gestreichelt...', 'Meinen Chef rickrollen...', @@ -1024,14 +1024,14 @@ export default { 'Die Schnozbeeren werden probiert...', "I'm going the distance, I'm going for speed...", 'Ist dies das wahre Leben? Ist dies nur Fantasie?...', - 'Ich habe ein gutes Gefuhl dabei...', - 'Den Baren wird gestupst...', + 'Ich habe ein gutes Gefühl dabei...', + 'Den Bären wird gestupst...', 'Recherche zu den neuesten Memes...', - 'Uberlege, wie ich das witziger machen kann...', + 'Überlege, wie ich das witziger machen kann...', 'Hmmm... lassen Sie mich nachdenken...', 'Wie nennt man einen Fisch ohne Augen? Ein Fsh...', 'Warum ging der Computer zur Therapie? Er hatte zu viele Bytes...', - 'Warum mogen Programmierer keine Natur? Sie hat zu viele Bugs...', + 'Warum mögen Programmierer keine Natur? Sie hat zu viele Bugs...', 'Warum bevorzugen Programmierer den Dunkelmodus? Weil Licht Bugs anzieht...', 'Warum ging der Entwickler pleite? Er hat seinen ganzen Cache aufgebraucht...', 'Was kann man mit einem kaputten Bleistift machen? Nichts, er ist sinnlos...', @@ -1046,28 +1046,28 @@ export default { 'Mein anderer Prozess ist eine TARDIS...', 'Mit dem Maschinengeist wird kommuniziert...', 'Die Gedanken marinieren lassen...', - 'Gerade erinnert, wo ich meine Schlussel hingelegt habe...', - 'Uber die Kugel wird nachgedacht...', - 'Ich habe Dinge gesehen, die Sie nicht glauben wurden... wie einen Benutzer, der Lademeldungen liest.', + 'Gerade erinnert, wo ich meine Schlüssel hingelegt habe...', + 'Über die Kugel wird nachgedacht...', + 'Ich habe Dinge gesehen, die Sie nicht glauben würden... wie einen Benutzer, der Lademeldungen liest.', 'Nachdenklicher Blick wird initiiert...', 'Was ist der Lieblingssnack eines Computers? Mikrochips.', 'Warum tragen Java-Entwickler Brillen? Weil sie nicht C#.', 'Der Laser wird aufgeladen... pew pew!', - 'Durch Null wird geteilt... nur Spass!', + 'Durch Null wird geteilt... nur Spaß!', 'Suche nach einem erwachsenen Aufseh... ich meine, Verarbeitung.', 'Es piept und boopt.', 'Pufferung... weil auch KIs einen Moment brauchen.', - 'Quantenteilchen werden fur schnellere Antwort verschrankt...', + 'Quantenteilchen werden für schnellere Antwort verschränkt...', 'Das Chrom wird poliert... an den Algorithmen.', 'Sind Sie nicht unterhalten? (Arbeite daran!)', - 'Die Code-Gremlins werden beschworen... zum Helfen, naturlich.', + 'Die Code-Gremlins werden beschworen... zum Helfen, natürlich.', 'Warte nur auf das Einwahlton-Ende...', 'Das Humor-O-Meter wird neu kalibriert.', 'Mein anderer Ladebildschirm ist noch lustiger.', - 'Ziemlich sicher, dass irgendwo eine Katze uber die Tastatur lauft...', - 'Verbessern... Verbessern... Ladt noch.', + 'Ziemlich sicher, dass irgendwo eine Katze über die Tastatur läuft...', + 'Verbessern... Verbessern... Lädt noch.', 'Das ist kein Bug, das ist ein Feature... dieses Ladebildschirms.', 'Haben Sie versucht, es aus- und wieder einzuschalten? (Den Ladebildschirm, nicht mich.)', - 'Zusatzliche Pylonen werden gebaut...', + 'Zusätzliche Pylonen werden gebaut...', ], }; From aaa66b3172b851efda4101bd860aa3db96728f05 Mon Sep 17 00:00:00 2001 From: LaZzyMan Date: Wed, 31 Dec 2025 17:38:33 +0800 Subject: [PATCH 31/65] fix: add tool result and deny warning in text mode --- packages/cli/src/nonInteractiveCli.test.ts | 84 ++++++++++++++++++- packages/cli/src/nonInteractiveCli.ts | 45 +++++++--- packages/cli/src/utils/errors.test.ts | 95 +++++++++++++++++++++- packages/cli/src/utils/errors.ts | 19 ++++- 4 files changed, 229 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 07fd168fc..74f30b342 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -298,7 +298,9 @@ describe('runNonInteractive', () => { mockConfig, expect.objectContaining({ name: 'testTool' }), expect.any(AbortSignal), - undefined, + expect.objectContaining({ + outputUpdateHandler: expect.any(Function), + }), ); // Verify first call has isContinuation: false expect(mockGeminiClient.sendMessageStream).toHaveBeenNthCalledWith( @@ -1777,4 +1779,84 @@ describe('runNonInteractive', () => { { isContinuation: false }, ); }); + + it('should print tool output to console in text mode (non-Task tools)', async () => { + // Test that tool output is printed to stdout in text mode + const toolCallEvent: ServerGeminiStreamEvent = { + type: GeminiEventType.ToolCallRequest, + value: { + callId: 'tool-1', + name: 'run_in_terminal', + args: { command: 'npm outdated' }, + isClientInitiated: false, + prompt_id: 'prompt-id-tool-output', + }, + }; + + // Mock tool execution with outputUpdateHandler being called + mockCoreExecuteToolCall.mockImplementation( + async (_config, _request, _signal, options) => { + // Simulate tool calling outputUpdateHandler with output chunks + if (options?.outputUpdateHandler) { + options.outputUpdateHandler('tool-1', 'Package outdated\n'); + options.outputUpdateHandler('tool-1', 'npm@1.0.0 -> npm@2.0.0\n'); + } + return { + responseParts: [ + { + functionResponse: { + id: 'tool-1', + name: 'run_in_terminal', + response: { + output: 'Package outdated\nnpm@1.0.0 -> npm@2.0.0', + }, + }, + }, + ], + }; + }, + ); + + const firstCallEvents: ServerGeminiStreamEvent[] = [ + toolCallEvent, + { + type: GeminiEventType.Finished, + value: { reason: undefined, usageMetadata: { totalTokenCount: 5 } }, + }, + ]; + + const secondCallEvents: ServerGeminiStreamEvent[] = [ + { type: GeminiEventType.Content, value: 'Dependencies checked' }, + { + type: GeminiEventType.Finished, + value: { reason: undefined, usageMetadata: { totalTokenCount: 3 } }, + }, + ]; + + mockGeminiClient.sendMessageStream + .mockReturnValueOnce(createStreamFromEvents(firstCallEvents)) + .mockReturnValueOnce(createStreamFromEvents(secondCallEvents)); + + await runNonInteractive( + mockConfig, + mockSettings, + 'Check dependencies', + 'prompt-id-tool-output', + ); + + // Verify that executeToolCall was called with outputUpdateHandler + expect(mockCoreExecuteToolCall).toHaveBeenCalledWith( + mockConfig, + expect.objectContaining({ name: 'run_in_terminal' }), + expect.any(AbortSignal), + expect.objectContaining({ + outputUpdateHandler: expect.any(Function), + }), + ); + + // Verify tool output was written to stdout + expect(processStdoutSpy).toHaveBeenCalledWith('Package outdated\n'); + expect(processStdoutSpy).toHaveBeenCalledWith('npm@1.0.0 -> npm@2.0.0\n'); + expect(processStdoutSpy).toHaveBeenCalledWith('Dependencies checked'); + }); }); diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index 067f190b9..17ac30eae 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -4,7 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { Config, ToolCallRequestInfo } from '@qwen-code/qwen-code-core'; +import type { + Config, + ToolCallRequestInfo, + ToolResultDisplay, +} from '@qwen-code/qwen-code-core'; import { isSlashCommand } from './ui/utils/commandUtils.js'; import type { LoadedSettings } from './config/settings.js'; import { @@ -333,7 +337,7 @@ export async function runNonInteractive( ? options.controlService.permission.getToolCallUpdateCallback() : undefined; - // Only pass outputUpdateHandler for Task tool + // Create output handler for Task tool (for subagent execution) const isTaskTool = finalRequestInfo.name === 'task'; const taskToolProgress = isTaskTool ? createTaskToolProgressHandler( @@ -343,20 +347,41 @@ export async function runNonInteractive( ) : undefined; const taskToolProgressHandler = taskToolProgress?.handler; + + // Create output handler for non-Task tools in text mode (for console output) + const nonTaskOutputHandler = + !isTaskTool && !adapter + ? (callId: string, outputChunk: ToolResultDisplay) => { + // Print tool output to console in text mode + if (typeof outputChunk === 'string') { + process.stdout.write(outputChunk); + } else if ( + outputChunk && + typeof outputChunk === 'object' && + 'ansiOutput' in outputChunk + ) { + // Handle ANSI output - just print as string for now + process.stdout.write(String(outputChunk.ansiOutput)); + } + } + : undefined; + + // Combine output handlers + const outputUpdateHandler = + taskToolProgressHandler || nonTaskOutputHandler; + const toolResponse = await executeToolCall( config, finalRequestInfo, abortController.signal, - isTaskTool && taskToolProgressHandler + outputUpdateHandler || toolCallUpdateCallback ? { - outputUpdateHandler: taskToolProgressHandler, - onToolCallsUpdate: toolCallUpdateCallback, - } - : toolCallUpdateCallback - ? { + ...(outputUpdateHandler && { outputUpdateHandler }), + ...(toolCallUpdateCallback && { onToolCallsUpdate: toolCallUpdateCallback, - } - : undefined, + }), + } + : undefined, ); // Note: In JSON mode, subagent messages are automatically added to the main diff --git a/packages/cli/src/utils/errors.test.ts b/packages/cli/src/utils/errors.test.ts index 818c3ac39..e3a27bd42 100644 --- a/packages/cli/src/utils/errors.test.ts +++ b/packages/cli/src/utils/errors.test.ts @@ -6,7 +6,11 @@ import { vi, type Mock, type MockInstance } from 'vitest'; import type { Config } from '@qwen-code/qwen-code-core'; -import { OutputFormat, FatalInputError } from '@qwen-code/qwen-code-core'; +import { + OutputFormat, + FatalInputError, + ToolErrorType, +} from '@qwen-code/qwen-code-core'; import { getErrorMessage, handleError, @@ -65,6 +69,7 @@ vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => { describe('errors', () => { let mockConfig: Config; let processExitSpy: MockInstance; + let processStderrWriteSpy: MockInstance; let consoleErrorSpy: MockInstance; beforeEach(() => { @@ -74,6 +79,11 @@ describe('errors', () => { // Mock console.error consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + // Mock process.stderr.write + processStderrWriteSpy = vi + .spyOn(process.stderr, 'write') + .mockImplementation(() => true); + // Mock process.exit to throw instead of actually exiting processExitSpy = vi.spyOn(process, 'exit').mockImplementation((code) => { throw new Error(`process.exit called with code: ${code}`); @@ -84,11 +94,13 @@ describe('errors', () => { getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT), getContentGeneratorConfig: vi.fn().mockReturnValue({ authType: 'test' }), getDebugMode: vi.fn().mockReturnValue(true), + isInteractive: vi.fn().mockReturnValue(false), } as unknown as Config; }); afterEach(() => { consoleErrorSpy.mockRestore(); + processStderrWriteSpy.mockRestore(); processExitSpy.mockRestore(); }); @@ -432,6 +444,87 @@ describe('errors', () => { expect(processExitSpy).not.toHaveBeenCalled(); }); }); + + describe('permission denied warnings', () => { + it('should show warning when EXECUTION_DENIED in non-interactive text mode', () => { + (mockConfig.getDebugMode as Mock).mockReturnValue(false); + (mockConfig.isInteractive as Mock).mockReturnValue(false); + ( + mockConfig.getOutputFormat as ReturnType + ).mockReturnValue(OutputFormat.TEXT); + + handleToolError( + toolName, + toolError, + mockConfig, + ToolErrorType.EXECUTION_DENIED, + ); + + expect(processStderrWriteSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'Warning: Tool "test-tool" requires user approval', + ), + ); + expect(processStderrWriteSpy).toHaveBeenCalledWith( + expect.stringContaining('use the -y flag (YOLO mode)'), + ); + expect(processExitSpy).not.toHaveBeenCalled(); + }); + + it('should not show warning when EXECUTION_DENIED in interactive mode', () => { + (mockConfig.getDebugMode as Mock).mockReturnValue(false); + (mockConfig.isInteractive as Mock).mockReturnValue(true); + ( + mockConfig.getOutputFormat as ReturnType + ).mockReturnValue(OutputFormat.TEXT); + + handleToolError( + toolName, + toolError, + mockConfig, + ToolErrorType.EXECUTION_DENIED, + ); + + expect(processStderrWriteSpy).not.toHaveBeenCalled(); + expect(processExitSpy).not.toHaveBeenCalled(); + }); + + it('should not show warning when EXECUTION_DENIED in JSON mode', () => { + (mockConfig.getDebugMode as Mock).mockReturnValue(false); + (mockConfig.isInteractive as Mock).mockReturnValue(false); + ( + mockConfig.getOutputFormat as ReturnType + ).mockReturnValue(OutputFormat.JSON); + + handleToolError( + toolName, + toolError, + mockConfig, + ToolErrorType.EXECUTION_DENIED, + ); + + expect(processStderrWriteSpy).not.toHaveBeenCalled(); + expect(processExitSpy).not.toHaveBeenCalled(); + }); + + it('should not show warning for non-EXECUTION_DENIED errors', () => { + (mockConfig.getDebugMode as Mock).mockReturnValue(false); + (mockConfig.isInteractive as Mock).mockReturnValue(false); + ( + mockConfig.getOutputFormat as ReturnType + ).mockReturnValue(OutputFormat.TEXT); + + handleToolError( + toolName, + toolError, + mockConfig, + ToolErrorType.FILE_NOT_FOUND, + ); + + expect(processStderrWriteSpy).not.toHaveBeenCalled(); + expect(processExitSpy).not.toHaveBeenCalled(); + }); + }); }); describe('handleCancellationError', () => { diff --git a/packages/cli/src/utils/errors.ts b/packages/cli/src/utils/errors.ts index 5338fa2fd..f804a630c 100644 --- a/packages/cli/src/utils/errors.ts +++ b/packages/cli/src/utils/errors.ts @@ -11,6 +11,7 @@ import { parseAndFormatApiError, FatalTurnLimitedError, FatalCancellationError, + ToolErrorType, } from '@qwen-code/qwen-code-core'; export function getErrorMessage(error: unknown): string { @@ -102,10 +103,24 @@ export function handleToolError( toolName: string, toolError: Error, config: Config, - _errorCode?: string | number, + errorCode?: string | number, resultDisplay?: string, ): void { - // Always just log to stderr; JSON/streaming formatting happens in the tool_result block elsewhere + // Check if this is a permission denied error in non-interactive mode + const isExecutionDenied = errorCode === ToolErrorType.EXECUTION_DENIED; + const isNonInteractive = !config.isInteractive(); + const isTextMode = config.getOutputFormat() === OutputFormat.TEXT; + + // Show warning for permission denied errors in non-interactive text mode + if (isExecutionDenied && isNonInteractive && isTextMode) { + const warningMessage = + `Warning: Tool "${toolName}" requires user approval but cannot execute in non-interactive mode.\n` + + `To enable automatic tool execution, use the -y flag (YOLO mode):\n` + + `Example: qwen -p 'your prompt' -y\n\n`; + process.stderr.write(warningMessage); + } + + // Always log detailed error in debug mode if (config.getDebugMode()) { console.error( `Error executing tool ${toolName}: ${resultDisplay || toolError.message}`, From e4caa7a8564e715d4ece2c87d5d5a28c06ae6c09 Mon Sep 17 00:00:00 2001 From: skyfire Date: Wed, 31 Dec 2025 20:15:51 +0800 Subject: [PATCH 32/65] for partial message processing and event timeout processing --- packages/sdk-java/pom.xml | 20 +++ .../com/alibaba/qwen/code/cli/Options.java | 6 - .../com/alibaba/qwen/code/cli/QwenCli.java | 54 ------ .../alibaba/qwen/code/cli/QwenCodeCli.java | 70 ++++++++ .../cli/protocol/data/AssistantContent.java | 6 + .../assistant/SDKAssistantMessage.java | 5 + .../assistant/SDKPartialAssistantMessage.java | 55 ++++++ .../message/assistant/block/ContentBlock.java | 3 +- .../message/assistant/block/TextBlock.java | 5 + .../assistant/block/ThinkingBlock.java | 5 + .../message/assistant/block/ToolUseBlock.java | 5 + .../event/ContentBlockDeltaEvent.java | 96 +++++++++++ .../event/ContentBlockStartEvent.java | 13 ++ .../event/ContentBlockStopEvent.java | 16 ++ .../event/MessageStartStreamEvent.java | 47 +++++ .../event/MessageStopStreamEvent.java | 7 + .../message/assistant/event/StreamEvent.java | 18 ++ .../control/CLIControlSetModelResponse.java | 22 +++ .../qwen/code/cli/session/Session.java | 161 ++++++++++-------- .../session/event/SessionEventConsumers.java | 22 +++ .../event/SessionEventSimpleConsumers.java | 115 ++++++++++++- .../qwen/code/cli/transport/Transport.java | 2 + .../code/cli/transport/TransportOptions.java | 79 ++++++--- .../transport/process/ProcessTransport.java | 147 ++++++++-------- .../process/TransportOptionsAdapter.java | 22 ++- .../code/cli/utils/MyConcurrentUtils.java | 65 +++++++ .../qwen/code/cli/utils/ThreadPoolConfig.java | 43 +++++ .../alibaba/qwen/code/cli/utils/Timeout.java | 27 +++ ...{QwenCliTest.java => QwenCodeCliTest.java} | 11 +- .../qwen/code/cli/session/SessionTest.java | 41 ++++- 30 files changed, 934 insertions(+), 254 deletions(-) delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java rename packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/{QwenCliTest.java => QwenCodeCliTest.java} (58%) diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 45c9ea895..346731815 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -25,6 +25,7 @@ 1.8 UTF-8 3.6.0 + 0.8.12 5.14.1 1.3.16 2.0.60 @@ -81,6 +82,25 @@ + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + + prepare-agent + + + + report + test + + report + + + + diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java deleted file mode 100644 index 82b0a4652..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/Options.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.alibaba.qwen.code.cli; - -import com.alibaba.qwen.code.cli.transport.TransportOptions; - -public class Options extends TransportOptions { -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java deleted file mode 100644 index 0471ab692..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCli.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.alibaba.qwen.code.cli; - -import java.util.ArrayList; -import java.util.List; - -import com.alibaba.qwen.code.cli.protocol.message.Message; -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.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.process.ProcessTransport; - -public class QwenCli { - public static List query(String prompt) { - Transport transport; - try { - transport = new ProcessTransport(); - } 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); - } - - final List response = new ArrayList<>(); - try { - session.sendPrompt(prompt, new SessionEventSimpleConsumers() { - @Override - public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { - response.add(systemMessage); - } - - @Override - public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) { - response.add(assistantMessage); - } - }); - } catch (Exception e) { - throw new RuntimeException("sendPrompt error!", e); - } - - try { - session.close(); - } catch (Exception e) { - throw new RuntimeException("close Session error!", e); - } - return response; - } -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java new file mode 100644 index 000000000..591552fc3 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java @@ -0,0 +1,70 @@ +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; + +public class QwenCodeCli { + public static List simpleQuery(String prompt) { + final List response = new ArrayList<>(); + MyConcurrentUtils.runAndWait(() -> simpleQuery(prompt, response::add), Timeout.TIMEOUT_30_MINUTES); + return response; + } + + public static void simpleQuery(String prompt, Consumer messageConsumer) { + Session session = newSessionWithProcessTransport(new TransportOptions()); + try { + session.sendPrompt(prompt, new SessionEventSimpleConsumers() { + @Override + public void onAssistantMessageIncludePartial(Session session, List 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); + } + + try { + session.close(); + } catch (Exception e) { + throw new RuntimeException("close Session error!", e); + } + } + + public static Session newSessionWithProcessTransport(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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java new file mode 100644 index 000000000..40d7f520d --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java @@ -0,0 +1,6 @@ +package com.alibaba.qwen.code.cli.protocol.data; + +public interface AssistantContent { + String getType(); + Object getContent(); +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java index 7e906fc44..b0a3012c4 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java @@ -15,6 +15,11 @@ public class SDKAssistantMessage extends MessageBase { @JSONField(name = "parent_tool_use_id") private String parentToolUseId; + public SDKAssistantMessage() { + super(); + this.type = "assistant"; + } + public String getUuid() { return uuid; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java new file mode 100644 index 000000000..a9ac24d05 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java index 3e72ad7d0..d40200c6e 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java @@ -4,9 +4,10 @@ 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 class ContentBlock { +public abstract class ContentBlock implements AssistantContent { protected String type; protected List annotations; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java index 86e5513d3..7a8cf7d43 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java @@ -13,4 +13,9 @@ public class TextBlock extends ContentBlock { public void setText(String text) { this.text = text; } + + @Override + public Object getContent() { + return text; + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java index fa479563f..4a133840f 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java @@ -22,4 +22,9 @@ public class ThinkingBlock extends ContentBlock{ public void setSignature(String signature) { this.signature = signature; } + + @Override + public Object getContent() { + return thinking; + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java index 58a3bd4fc..ef5de8b02 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java @@ -46,4 +46,9 @@ public class ToolUseBlock extends ContentBlock { public void setAnnotations(List annotations) { this.annotations = annotations; } + + @Override + public Object getContent() { + return input; + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java new file mode 100644 index 000000000..78b7961cc --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java @@ -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; + } + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java new file mode 100644 index 000000000..884558512 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java @@ -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; +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java new file mode 100644 index 000000000..0e950f817 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java new file mode 100644 index 000000000..88be40545 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java new file mode 100644 index 000000000..3ea04bc50 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java @@ -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{ +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java new file mode 100644 index 000000000..d288402fa --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java new file mode 100644 index 000000000..71d6b0e38 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java @@ -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; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java index 79a210742..c3e605476 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java @@ -2,6 +2,8 @@ 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; @@ -15,6 +17,7 @@ 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; @@ -28,16 +31,19 @@ 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 static final Logger log = LoggerFactory.getLogger(Session.class); + private final Timeout defaultEventTimeout = Timeout.TIMEOUT_60_SECONDS; public Session(Transport transport) throws SessionControlException { if (transport == null || !transport.isAvailable()) { @@ -61,43 +67,44 @@ public class Session { } } - public void interrupt() throws SessionControlException { - if (!isAvailable()) { - throw new SessionControlException("Session is not available"); - } - + public void close() throws SessionControlException { try { - transport.inputNoWaitResponse( - new CLIControlRequest().setRequest(new CLIControlInterruptRequest()).toString()); + transport.close(); } catch (Exception e) { - throw new SessionControlException("Failed to interrupt the session", e); + throw new SessionControlException("Failed to close the session", e); } } - public void setModel(String modelName) throws SessionControlException { - if (!isAvailable()) { - throw new SessionControlException("Session is not available"); - } + public Optional interrupt() throws SessionControlException { + checkAvailable(); + return processControlRequest(new CLIControlRequest().setRequest(new CLIControlInterruptRequest()).toString()); + } + public Optional setModel(String modelName) throws SessionControlException { + checkAvailable(); CLIControlSetModelRequest cliControlSetModelRequest = new CLIControlSetModelRequest(); cliControlSetModelRequest.setModel(modelName); - try { - transport.inputNoWaitResponse(new CLIControlRequest().setRequest(cliControlSetModelRequest).toString()); - } catch (Exception e) { - throw new SessionControlException("Failed to set model", e); - } + return processControlRequest(new CLIControlRequest().setRequest(cliControlSetModelRequest).toString()); } - public void setPermissionMode(PermissionMode permissionMode) throws SessionControlException { - if (!isAvailable()) { - throw new SessionControlException("Session is not available"); - } - + public Optional setPermissionMode(PermissionMode permissionMode) throws SessionControlException { + checkAvailable(); CLIControlSetPermissionModeRequest cliControlSetPermissionModeRequest = new CLIControlSetPermissionModeRequest(); cliControlSetPermissionModeRequest.setMode(permissionMode.getValue()); + return processControlRequest( + new CLIControlRequest().setRequest(cliControlSetPermissionModeRequest).toString()); + } + + private Optional processControlRequest(String request) throws SessionControlException { try { - transport.inputNoWaitResponse( - new CLIControlRequest().setRequest(cliControlSetPermissionModeRequest).toString()); + if (transport.isReading()) { + transport.inputNoWaitResponse(request); + return Optional.empty(); + } else { + String response = transport.inputWaitForOneLine(request); + CLIControlResponse cliControlResponse = JSON.parseObject(response, new TypeReference>() {}); + return Optional.of("success".equals(cliControlResponse.getResponse().getSubtype())); + } } catch (Exception e) { throw new SessionControlException("Failed to set model", e); } @@ -108,61 +115,43 @@ public class Session { } public void resumeSession(String sessionId) throws SessionControlException { - if (!isAvailable()) { - throw new SessionControlException("Session is not available"); - } - if (StringUtils.isNotBlank(sessionId)) { transport.getTransportOptions().setResumeSessionId(sessionId); } this.start(); } - public String getSessionId() { - return Optional.ofNullable(lastSdkSystemMessage).map(SDKSystemMessage::getSessionId).orElse(null); - } - - public void close() throws SessionControlException { - try { - transport.close(); - } catch (Exception e) { - throw new SessionControlException("Failed to close the session", e); - } - } - - public boolean isAvailable() { - return transport.isAvailable(); - } - - public Capabilities getCapabilities() { - return Optional.ofNullable(lastCliControlInitializeResponse).map(CLIControlInitializeResponse::getCapabilities).orElse(new Capabilities()); - } - - public void sendPrompt(String prompt, SessionEventConsumers sessionEventConsumers) throws SessionSendPromptException { - if (!transport.isAvailable()) { - throw new SessionSendPromptException("Session is not available"); - } - + public void sendPrompt(String prompt, SessionEventConsumers sessionEventConsumers) throws SessionSendPromptException, SessionControlException { + checkAvailable(); try { transport.inputWaitForMultiLine(new SDKUserMessage().setContent(prompt).toString(), (line) -> { - log.debug("read a message from agent {}", line); JSONObject jsonObject = JSON.parseObject(line); String messageType = jsonObject.getString("type"); if ("system".equals(messageType)) { lastSdkSystemMessage = jsonObject.to(SDKSystemMessage.class); - sessionEventConsumers.onSystemMessage(this, lastSdkSystemMessage); + MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onSystemMessage(this, lastSdkSystemMessage), + Optional.ofNullable(sessionEventConsumers.onSystemMessageTimeout(this)).orElse(defaultEventTimeout)); return false; } else if ("assistant".equals(messageType)) { - sessionEventConsumers.onAssistantMessage(this, jsonObject.to(SDKAssistantMessage.class)); + 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)) { - sessionEventConsumers.onUserMessage(this, jsonObject.to(SDKUserMessage.class, Feature.FieldBased)); + 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)) { - sessionEventConsumers.onResultMessage(this, jsonObject.to(SDKResultMessage.class)); + MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onResultMessage(this, jsonObject.to(SDKResultMessage.class)), + Optional.ofNullable(sessionEventConsumers.onResultMessageTimeout(this)).orElse(defaultEventTimeout)); return true; } else if ("control_response".equals(messageType)) { - sessionEventConsumers.onControlResponse(this, jsonObject.to(CLIControlResponse.class)); + 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 { @@ -170,10 +159,11 @@ public class Session { return "error".equals(jsonObject.getString("subtype")); } } else if ("control_request".equals(messageType)) { - return processControlRequest(jsonObject, sessionEventConsumers); + return processControlRequestInThePrompting(jsonObject, sessionEventConsumers); } else { log.warn("unknown message type: {}", messageType); - sessionEventConsumers.onOtherMessage(this, line); + MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onOtherMessage(this, line), + Optional.ofNullable(sessionEventConsumers.onOtherMessageTimeout(this)).orElse(defaultEventTimeout)); return false; } }); @@ -182,7 +172,7 @@ public class Session { } } - private boolean processControlRequest(JSONObject jsonObject, SessionEventConsumers sessionEventConsumers) { + private boolean processControlRequestInThePrompting(JSONObject jsonObject, SessionEventConsumers sessionEventConsumers) { String subType = Optional.of(jsonObject) .map(cr -> cr.getJSONObject("request")) .map(r -> r.getString("subtype")) @@ -190,13 +180,21 @@ public class Session { if ("can_use_tool".equals(subType)) { try { return processPermissionResponse(jsonObject, sessionEventConsumers); - } catch (IOException e) { + } catch (IOException | ExecutionException | InterruptedException | TimeoutException e) { log.error("Failed to process permission response", e); return false; } } else { - CLIControlResponse cliControlResponse = sessionEventConsumers.onControlRequest(this, - jsonObject.to(new TypeReference>() {})); + CLIControlResponse cliControlResponse; + try { + cliControlResponse = MyConcurrentUtils.runAndWait( + () -> sessionEventConsumers.onControlRequest(this, jsonObject.to(new TypeReference>() {})), + 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()); @@ -209,9 +207,13 @@ public class Session { } } - private boolean processPermissionResponse(JSONObject jsonObject, SessionEventConsumers sessionEventConsumers) throws IOException { - CLIControlRequest permissionRequest = jsonObject.to(new TypeReference>() {}); - Behavior behavior = Optional.ofNullable(sessionEventConsumers.onPermissionRequest(this, permissionRequest)) + private boolean processPermissionResponse(JSONObject jsonObject, SessionEventConsumers sessionEventConsumers) + throws IOException, ExecutionException, InterruptedException, TimeoutException { + CLIControlRequest permissionRequest = jsonObject.to( + new TypeReference>() {}); + + 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; @@ -223,11 +225,30 @@ public class Session { }) .orElse(Behavior.defaultBehavior()); CLIControlResponse permissionResponse = new CLIControlResponse<>(); - permissionResponse.createResponse().setResponse(new CLIControlPermissionResponse().setBehavior(behavior)).setRequestId(permissionRequest.getRequestId()); + 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"); + } + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java index e2100b5cc..686620cfa 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java @@ -5,10 +5,12 @@ 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); @@ -17,6 +19,8 @@ public interface SessionEventConsumers { void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage); + void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage); + void onUserMessage(Session session, SDKUserMessage userMessage); void onOtherMessage(Session session, String message); @@ -26,4 +30,22 @@ public interface SessionEventConsumers { CLIControlResponse onControlRequest(Session session, CLIControlRequest cliControlRequest); Behavior onPermissionRequest(Session session, CLIControlRequest 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); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java index 9c685e755..fe21bbe96 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java @@ -1,14 +1,28 @@ 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 @@ -21,6 +35,22 @@ public class SessionEventSimpleConsumers implements SessionEventConsumers { @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 assistantContents, + AssistantMessageOutputType assistantMessageOutputType) { } @Override @@ -42,6 +72,89 @@ public class SessionEventSimpleConsumers implements SessionEventConsumers { @Override public Behavior onPermissionRequest(Session session, CLIControlRequest permissionRequest) { - return Behavior.defaultBehavior(); + 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 } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java index b3d69ee28..af4266f45 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java @@ -8,6 +8,8 @@ import java.util.function.Function; public interface Transport { TransportOptions getTransportOptions(); + boolean isReading(); + void start() throws IOException; void close() throws IOException; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java index b5e6ada6f..7e274d1a0 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java @@ -4,6 +4,7 @@ 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; @@ -17,120 +18,154 @@ public class TransportOptions implements Cloneable { private List allowedTools; private String authType; private Boolean includePartialMessages; - private Long turnTimeoutMs; - private Long messageTimeoutMs; + private Boolean skillsEnable; + private Timeout turnTimeout; + private Timeout messageTimeout; private String resumeSessionId; + private List otherOptions; public String getPathToQwenExecutable() { return pathToQwenExecutable; } - public void setPathToQwenExecutable(String pathToQwenExecutable) { + public TransportOptions setPathToQwenExecutable(String pathToQwenExecutable) { this.pathToQwenExecutable = pathToQwenExecutable; + return this; } public String getCwd() { return cwd; } - public void setCwd(String cwd) { + public TransportOptions setCwd(String cwd) { this.cwd = cwd; + return this; } public String getModel() { return model; } - public void setModel(String model) { + public TransportOptions setModel(String model) { this.model = model; + return this; } public PermissionMode getPermissionMode() { return permissionMode; } - public void setPermissionMode(PermissionMode permissionMode) { + public TransportOptions setPermissionMode(PermissionMode permissionMode) { this.permissionMode = permissionMode; + return this; } public Map getEnv() { return env; } - public void setEnv(Map env) { + public TransportOptions setEnv(Map env) { this.env = env; + return this; } public Integer getMaxSessionTurns() { return maxSessionTurns; } - public void setMaxSessionTurns(Integer maxSessionTurns) { + public TransportOptions setMaxSessionTurns(Integer maxSessionTurns) { this.maxSessionTurns = maxSessionTurns; + return this; } public List getCoreTools() { return coreTools; } - public void setCoreTools(List coreTools) { + public TransportOptions setCoreTools(List coreTools) { this.coreTools = coreTools; + return this; } public List getExcludeTools() { return excludeTools; } - public void setExcludeTools(List excludeTools) { + public TransportOptions setExcludeTools(List excludeTools) { this.excludeTools = excludeTools; + return this; } public List getAllowedTools() { return allowedTools; } - public void setAllowedTools(List allowedTools) { + public TransportOptions setAllowedTools(List allowedTools) { this.allowedTools = allowedTools; + return this; } public String getAuthType() { return authType; } - public void setAuthType(String authType) { + public TransportOptions setAuthType(String authType) { this.authType = authType; + return this; } public Boolean getIncludePartialMessages() { return includePartialMessages; } - public void setIncludePartialMessages(Boolean includePartialMessages) { + public TransportOptions setIncludePartialMessages(Boolean includePartialMessages) { this.includePartialMessages = includePartialMessages; + return this; } - public Long getTurnTimeoutMs() { - return turnTimeoutMs; + public Boolean getSkillsEnable() { + return skillsEnable; } - public void setTurnTimeoutMs(Long turnTimeoutMs) { - this.turnTimeoutMs = turnTimeoutMs; + public TransportOptions setSkillsEnable(Boolean skillsEnable) { + this.skillsEnable = skillsEnable; + return this; } - public Long getMessageTimeoutMs() { - return messageTimeoutMs; + public Timeout getTurnTimeout() { + return turnTimeout; } - public void setMessageTimeoutMs(Long messageTimeoutMs) { - this.messageTimeoutMs = messageTimeoutMs; + 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 void setResumeSessionId(String resumeSessionId) { + public TransportOptions setResumeSessionId(String resumeSessionId) { this.resumeSessionId = resumeSessionId; + return this; + } + + public List getOtherOptions() { + return otherOptions; + } + + public TransportOptions setOtherOptions(List otherOptions) { + this.otherOptions = otherOptions; + return this; } @Override diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java index 14a0eb0ff..11be50ecc 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java @@ -2,6 +2,8 @@ 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; @@ -14,29 +16,37 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.lang.ProcessBuilder.Redirect; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; 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 Long turnTimeoutMs; - protected Long messageTimeoutMs; + protected Timeout turnTimeout; + protected Timeout messageTimeout; protected Process process; protected BufferedWriter processInput; protected BufferedReader processOutput; protected BufferedReader processError; + protected final Consumer 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 errorHandler) throws IOException { this.transportOptions = transportOptions; + this.errorHandler = errorHandler; start(); } @@ -45,11 +55,16 @@ public class ProcessTransport implements Transport { return transportOptions; } + @Override + public boolean isReading() { + return reading.get(); + } + @Override public void start() throws IOException { TransportOptionsAdapter transportOptionsAdapter = new TransportOptionsAdapter(transportOptions); - this.turnTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeoutMs(); - this.messageTimeoutMs = transportOptionsAdapter.getHandledTransportOptions().getMessageTimeoutMs(); + this.turnTimeout = transportOptionsAdapter.getHandledTransportOptions().getTurnTimeout(); + this.messageTimeout = transportOptionsAdapter.getHandledTransportOptions().getMessageTimeout(); String[] commandArgs = transportOptionsAdapter.buildCommandArgs(); log.debug("trans to command args: {}", transportOptionsAdapter); @@ -91,113 +106,87 @@ public class ProcessTransport implements Transport { @Override public String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException { - return inputWaitForOneLine(message, turnTimeoutMs); + return inputWaitForOneLine(message, turnTimeout); } - private String inputWaitForOneLine(String message, long timeOutInMs) + private String inputWaitForOneLine(String message, Timeout timeOut) throws IOException, TimeoutException, InterruptedException, ExecutionException { inputNoWaitResponse(message); - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - return processOutput.readLine(); - } catch (IOException e) { - throw new ContextedRuntimeException("read line error", e) - .addContextValue("message", message); - } - }); - try { - String line = future.get(timeOutInMs, TimeUnit.MILLISECONDS); + 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; - } catch (TimeoutException e) { - future.cancel(true); - log.warn("read message timeout {}, canceled readOneLine task", timeOutInMs, e); - throw e; - } catch (InterruptedException e) { - future.cancel(true); - log.warn("interrupted task, canceled task", e); - throw e; - } catch (ExecutionException e) { - future.cancel(true); - log.warn("the readOneLine task execute error", e); - throw e; + } finally { + reading.set(false); } } @Override public void inputWaitForMultiLine(String message, Function callBackFunction) throws IOException { - inputWaitForMultiLine(message, callBackFunction, turnTimeoutMs); + inputWaitForMultiLine(message, callBackFunction, turnTimeout); } - private void inputWaitForMultiLine(String message, Function callBackFunction, long timeOutInMs) throws IOException { + private void inputWaitForMultiLine(String message, Function callBackFunction, Timeout timeOut) throws IOException { log.debug("input message for multiLine: {}", message); inputNoWaitResponse(message); - - CompletableFuture future = CompletableFuture.runAsync(() -> iterateOutput(callBackFunction)); - try { - future.get(timeOutInMs, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - future.cancel(true); - log.warn("read message timeout {}, canceled readMultiMessages task", timeOutInMs, e); - } catch (InterruptedException e) { - future.cancel(true); - log.warn("interrupted task, canceled task", e); - } catch (ExecutionException e) { - future.cancel(true); - log.warn("the readMultiMessages task execute error", e); - } catch (Exception e) { - future.cancel(true); - log.warn("other error"); - } + MyConcurrentUtils.runAndWait(() -> iterateOutput(callBackFunction), timeOut); } @Override public void inputNoWaitResponse(String message) throws IOException { - log.debug("input message to agent: {}", message); + log.debug("input message to process: {}", message); processInput.write(message); processInput.newLine(); processInput.flush(); } private void startErrorReading() { - CompletableFuture.runAsync(() -> { + MyConcurrentUtils.asyncRun(() -> { try { - String line; - while ((line = processError.readLine()) != null) { - System.err.println("错误: " + line); - } - } catch (Exception e) { - System.err.println("错误: " + e.getMessage()); - } - }); - } - - private void iterateOutput(Function callBackFunction) { - CompletableFuture future = CompletableFuture.runAsync(() -> { - try { - for (String line = processOutput.readLine(); line != null; line = processOutput.readLine()) { - log.debug("read a message from agent {}", line); - if (callBackFunction.apply(line)) { + 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) { - throw new RuntimeException("read process output error", 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 callBackFunction) { try { - future.get(messageTimeoutMs, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - log.warn("read message 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); + 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); } } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java index 1f179f93e..a2226a499 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java @@ -1,6 +1,7 @@ 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; @@ -11,11 +12,12 @@ 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 Long DEFAULT_TURN_TIMEOUT_MS = 1000 * 60 * 30L; - private static final Long DEFAULT_MESSAGE_TIMEOUT_MS = 1000 * 60 * 3L; + 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); @@ -73,10 +75,18 @@ class TransportOptionsAdapter { 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[] {}); } @@ -97,12 +107,12 @@ class TransportOptionsAdapter { Optional.ofNullable(transportOptions.getEnv()).ifPresent(env::putAll); transportOptions.setEnv(env); - if (transportOptions.getTurnTimeoutMs() == null) { - transportOptions.setTurnTimeoutMs(DEFAULT_TURN_TIMEOUT_MS); + if (transportOptions.getTurnTimeout() == null) { + transportOptions.setTurnTimeout(DEFAULT_TURN_TIMEOUT); } - if (transportOptions.getMessageTimeoutMs() == null) { - transportOptions.setMessageTimeoutMs(DEFAULT_MESSAGE_TIMEOUT_MS); + if (transportOptions.getMessageTimeout() == null) { + transportOptions.setMessageTimeout(DEFAULT_MESSAGE_TIMEOUT); } return transportOptions; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java new file mode 100644 index 000000000..3b5cf22da --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java @@ -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 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 runAndWait(Supplier supplier, Timeout timeOut) + throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture 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 errorCallback) { + CompletableFuture future = CompletableFuture.runAsync(() -> { + try { + runnable.run(); + } catch (Exception e) { + log.warn("async task error", e); + } + }, ThreadPoolConfig.getExecutor()); + future.whenComplete(errorCallback); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java new file mode 100644 index 000000000..59b7ef4cb --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java @@ -0,0 +1,43 @@ +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 executorSupplier; + public static void setExecutorSupplier(Supplier executorSupplier) { + ThreadPoolConfig.executorSupplier = executorSupplier; + } + + public static ExecutorService getExecutor() { + return Optional.ofNullable(executorSupplier).map(s -> { + try { + return s.get(); + } catch (Exception e) { + return defaultExecutor; + } + }).orElse(defaultExecutor); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java new file mode 100644 index 000000000..f00b39085 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java @@ -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); +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCodeCliTest.java similarity index 58% rename from packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java rename to packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCodeCliTest.java index 01295ce6c..51be8bf4c 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCliTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCodeCliTest.java @@ -2,21 +2,18 @@ package com.alibaba.qwen.code.cli; import java.util.List; -import com.alibaba.qwen.code.cli.protocol.message.Message; -import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; - import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.junit.jupiter.api.Assertions.*; -class QwenCliTest { +class QwenCodeCliTest { - private static final Logger log = LoggerFactory.getLogger(QwenCliTest.class); + private static final Logger log = LoggerFactory.getLogger(QwenCodeCliTest.class); @Test - void query() { - List result = QwenCli.query("hello world"); + void simpleQuery() { + List result = QwenCodeCli.simpleQuery("hello world"); log.info("result: {}", result); assertNotNull(result); } diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java index 51c37c6c8..8cdfa03c1 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java @@ -1,11 +1,14 @@ package com.alibaba.qwen.code.cli.session; import java.io.IOException; +import java.util.List; import com.alibaba.fastjson2.JSON; +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; @@ -17,6 +20,7 @@ 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.Transport; +import com.alibaba.qwen.code.cli.transport.TransportOptions; import com.alibaba.qwen.code.cli.transport.process.ProcessTransport; import org.apache.commons.lang3.StringUtils; @@ -28,18 +32,34 @@ class SessionTest { private static final Logger log = LoggerFactory.getLogger(SessionTest.class); + @Test + void partialSendPromptSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { + Transport transport = new ProcessTransport(new TransportOptions().setIncludePartialMessages(true)); + Session session = new Session(transport); + session.sendPrompt("in the dir src/test/temp/, create file empty file test.touch", new SessionEventSimpleConsumers() { + @Override + public void onAssistantMessageIncludePartial(Session session, List assistantContents, + AssistantMessageOutputType assistantMessageOutputType) { + log.info("onAssistantMessageIncludePartial: {}", JSON.toJSONString(assistantContents)); + } + }.setDefaultPermissionOperation(Operation.allow)); + } + @Test void setPermissionModeSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { Transport transport = new ProcessTransport(); Session session = new Session(transport); - session.setPermissionMode(PermissionMode.YOLO); + 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()); - session.setPermissionMode(PermissionMode.PLAN); + 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()); - session.setPermissionMode(PermissionMode.AUTO_EDIT); + 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() { @@ -57,19 +77,19 @@ class SessionTest { Transport transport = new ProcessTransport(); Session session = new Session(transport); - session.setModel("qwen3-coder-flash"); + 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"); - session.setModel("qwen3-coder-plus"); + 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"); - session.setModel("qwen3-max"); + 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()); @@ -129,12 +149,17 @@ class SessionTest { } public void writeSplitLine(String line) { - log.info("{} {}",line, StringUtils.repeat("=", 300)); + 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}}}"; + 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); } From 30f9e9c7828752709f4737a9df0702983815ec13 Mon Sep 17 00:00:00 2001 From: skyfire Date: Wed, 31 Dec 2025 22:57:20 +0800 Subject: [PATCH 33/65] for README.md --- packages/sdk-java/QWEN.md | 170 +++++ packages/sdk-java/README.md | 609 ++++++++++++++++++ .../alibaba/qwen/code/cli/QwenCodeCli.java | 25 +- .../qwen/code/cli/utils/ThreadPoolConfig.java | 6 +- .../qwen/code/cli/session/SessionTest.java | 25 +- packages/sdk-java/todo | 6 - 6 files changed, 815 insertions(+), 26 deletions(-) create mode 100644 packages/sdk-java/QWEN.md create mode 100644 packages/sdk-java/README.md delete mode 100644 packages/sdk-java/todo diff --git a/packages/sdk-java/QWEN.md b/packages/sdk-java/QWEN.md new file mode 100644 index 000000000..0ebb55c7d --- /dev/null +++ b/packages/sdk-java/QWEN.md @@ -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 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 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. diff --git a/packages/sdk-java/README.md b/packages/sdk-java/README.md new file mode 100644 index 000000000..0d7030658 --- /dev/null +++ b/packages/sdk-java/README.md @@ -0,0 +1,609 @@ +# 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 + + com.alibaba + qwencode-sdk-java + 0.0.1 + +``` + +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) + +> From v0.1.1, the CLI is bundled with the SDK. So no standalone CLI installation is needed. + +## 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 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. + +### 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, returns Behavior) +- `onAssistantMessageIncludePartial`: Handles assistant messages including partial content (specific to SessionEventSimpleConsumers, called by both onAssistantMessage and onPartialAssistantMessage) (receives Session, List, 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.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; + +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 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 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 +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.session.event.SessionEventSimpleConsumers; + +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 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 interrupt() throws SessionControlException` +- **Purpose**: Interrupts the current prompt processing without closing the session +- **Return value**: An `Optional` 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.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; + +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 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 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` 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.session.Session; +import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers; + +try (Session session = QwenCodeCli.newSession()) { + // Switch to a specific model + Optional 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 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 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` 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.session.Session; +import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers; +import com.alibaba.qwen.code.cli.protocol.data.PermissionMode; + +try (Session session = QwenCodeCli.newSession()) { + // Switch to a permissive mode + Optional 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 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.utils.Timeout; +import java.util.concurrent.TimeUnit; +import com.alibaba.qwen.code.cli.session.event.SessionEventConsumers; +import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers; + +// 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", sessionEventConsumers); +``` + +### 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` 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.utils.ThreadPoolConfig; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.function.Supplier; + +// Set a custom thread pool supplier +ThreadPoolConfig.setExecutorSupplier(new Supplier() { + @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.utils.ThreadPoolConfig; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +// 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. + +### 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.session.event.SessionEventSimpleConsumers; + +try (Session session = QwenCodeCli.newSession()) { + try { + List 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. diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java index 591552fc3..1e306dc15 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java @@ -16,7 +16,12 @@ 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 simpleQuery(String prompt) { final List response = new ArrayList<>(); MyConcurrentUtils.runAndWait(() -> simpleQuery(prompt, response::add), Timeout.TIMEOUT_30_MINUTES); @@ -24,7 +29,7 @@ public class QwenCodeCli { } public static void simpleQuery(String prompt, Consumer messageConsumer) { - Session session = newSessionWithProcessTransport(new TransportOptions()); + Session session = newSession(new TransportOptions()); try { session.sendPrompt(prompt, new SessionEventSimpleConsumers() { @Override @@ -42,16 +47,20 @@ public class QwenCodeCli { }.setDefaultPermissionOperation(Operation.allow)); } catch (Exception e) { throw new RuntimeException("sendPrompt error!", e); - } - - try { - session.close(); - } catch (Exception e) { - throw new RuntimeException("close Session error!", e); + } finally { + try { + session.close(); + } catch (Exception e) { + log.error("close session error!", e); + } } } - public static Session newSessionWithProcessTransport(TransportOptions transportOptions) { + public static Session newSession() { + return newSession(new TransportOptions()); + } + + public static Session newSession(TransportOptions transportOptions) { Transport transport; try { transport = new ProcessTransport(transportOptions); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java index 59b7ef4cb..9837ef798 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java @@ -31,7 +31,11 @@ public class ThreadPoolConfig { ThreadPoolConfig.executorSupplier = executorSupplier; } - public static ExecutorService getExecutor() { + public static ThreadPoolExecutor getDefaultExecutor() { + return defaultExecutor; + } + + static ExecutorService getExecutor() { return Optional.ofNullable(executorSupplier).map(s -> { try { return s.get(); diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java index 8cdfa03c1..9292d83fe 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java @@ -2,8 +2,10 @@ 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; @@ -19,9 +21,8 @@ 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.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.Timeout; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; @@ -34,8 +35,7 @@ class SessionTest { @Test void partialSendPromptSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { - Transport transport = new ProcessTransport(new TransportOptions().setIncludePartialMessages(true)); - Session session = new Session(transport); + 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 assistantContents, @@ -47,8 +47,7 @@ class SessionTest { @Test void setPermissionModeSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { - Transport transport = new ProcessTransport(); - Session session = new Session(transport); + 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")); @@ -74,8 +73,7 @@ class SessionTest { @Test void sendPromptAndSetModelSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { - Transport transport = new ProcessTransport(); - Session session = new Session(transport); + 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"); @@ -100,10 +98,10 @@ class SessionTest { @Test void sendPromptAndInterruptContinueSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { - Transport transport = new ProcessTransport(); - Session session = new Session(transport); + Session session = QwenCodeCli.newSession(); SessionEventConsumers sessionEventConsumers = new SessionEventSimpleConsumers() { + @Override public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { log.info("systemMessage: {}", systemMessage); @@ -133,7 +131,12 @@ class SessionTest { 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"); diff --git a/packages/sdk-java/todo b/packages/sdk-java/todo deleted file mode 100644 index 656489715..000000000 --- a/packages/sdk-java/todo +++ /dev/null @@ -1,6 +0,0 @@ -1、event timeout -2、mcp servers -3、errorHandle -4、review QwenCli -https://github.com/QwenLM/qwen-code/tree/main/packages/sdk-typescript#custom-permission-handler - From 6ff437671e2d51b9dc6e5b04f4a1ab3516cc9a5e Mon Sep 17 00:00:00 2001 From: skyfire Date: Wed, 31 Dec 2025 23:26:20 +0800 Subject: [PATCH 34/65] for README.md --- packages/sdk-java/README.md | 3 +-- packages/sdk-java/pom.xml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/sdk-java/README.md b/packages/sdk-java/README.md index 0d7030658..65dbfc7ae 100644 --- a/packages/sdk-java/README.md +++ b/packages/sdk-java/README.md @@ -26,8 +26,7 @@ implementation 'com.alibaba:qwencode-sdk-java:0.0.1' - Java >= 1.8 - Maven >= 3.6.0 (for building from source) - -> From v0.1.1, the CLI is bundled with the SDK. So no standalone CLI installation is needed. +- 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 diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 346731815..33de7f073 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -5,7 +5,7 @@ com.alibaba qwencode-sdk-java jar - 0.0.1 + 0.0.1-SNAPSHOT qwencode-sdk-java https://maven.apache.org From 6a62167f79a5e6e50fa525d3f626c107682aca09 Mon Sep 17 00:00:00 2001 From: skyfire Date: Wed, 31 Dec 2025 23:36:17 +0800 Subject: [PATCH 35/65] for README.md --- packages/sdk-java/README.md | 492 ++++++++++++++++++++---------------- packages/sdk-java/pom.xml | 4 +- 2 files changed, 283 insertions(+), 213 deletions(-) diff --git a/packages/sdk-java/README.md b/packages/sdk-java/README.md index 65dbfc7ae..149d634c4 100644 --- a/packages/sdk-java/README.md +++ b/packages/sdk-java/README.md @@ -135,6 +135,7 @@ Event processing is subject to the timeout settings configured in `TransportOpti 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; @@ -152,112 +153,116 @@ import com.alibaba.qwen.code.cli.utils.Timeout; import java.util.List; import java.util.concurrent.TimeUnit; -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); - } +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); - } + @Override + public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) { + System.out.println("Partial assistant message: " + partialAssistantMessage); + } - public void onAssistantMessageIncludePartial(Session session, List assistantContents, - AssistantMessageOutputType assistantMessageOutputType) { - System.out.println("Assistant content (type: " + assistantMessageOutputType + "): " + assistantContents); - } + public void onAssistantMessageIncludePartial(Session session, List 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 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 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 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 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 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 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 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 Behavior onPermissionRequest(Session session, CLIControlRequest 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 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 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 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 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 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 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 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 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 + @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); + session.sendPrompt("Example prompt", eventConsumers); + } +} ``` ### Permission Modes @@ -272,8 +277,17 @@ The SDK supports different permission modes for controlling tool execution: To set a permission mode: ```java -Session session = QwenCodeCli.newSession(new TransportOptions().setPermissionMode(PermissionMode.YOLO)); -session.setPermissionMode(PermissionMode.PLAN); +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 @@ -291,17 +305,26 @@ The SDK provides fine-grained control over session lifecycle and behavior: 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; -TransportOptions options = new TransportOptions() - .setModel("qwen-max") - .setPermissionMode(PermissionMode.AUTO_EDIT); +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 result = session.sendPrompt("Explain how to use the SDK", new SessionEventSimpleConsumers()); - result.forEach(System.out::println); -} // Session automatically closes when exiting try-with-resources + try (Session session = QwenCodeCli.newSession(options)) { + // Use the session with default event consumers + List 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 @@ -315,30 +338,36 @@ The `interrupt()` function allows you to interrupt a currently running prompt. T 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; -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("")); +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 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()); - } + // Interrupt the session after receiving the first message + try { + Optional 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()); + } + } + }); } - }); + } } ``` @@ -354,25 +383,31 @@ The `setModel()` function allows you to dynamically change the AI model during a 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; -try (Session session = QwenCodeCli.newSession()) { - // Switch to a specific model - Optional modelChangeResult = session.setModel("qwen3-coder-flash"); - System.out.println(modelChangeResult.map(s -> s ? "setModel success" : "setModel error") - .orElse("setModel unknown")); +public class SetModelExample { + public static void main(String[] args) { + try (Session session = QwenCodeCli.newSession()) { + // Switch to a specific model + Optional 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()); + // Use the model for a prompt + session.sendPrompt("hello world", new SessionEventSimpleConsumers()); - // Switch to another model - Optional modelChangeResult2 = session.setModel("qwen3-coder-plus"); - System.out.println(modelChangeResult2.map(s -> s ? "setModel success" : "setModel error") - .orElse("setModel unknown")); + // Switch to another model + Optional 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()); + // Use the new model for another prompt + session.sendPrompt("list files in the current directory", new SessionEventSimpleConsumers()); + } + } } ``` @@ -388,26 +423,32 @@ The `setPermissionMode()` function allows you to dynamically change the permissi 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; -try (Session session = QwenCodeCli.newSession()) { - // Switch to a permissive mode - Optional permissionChangeResult = session.setPermissionMode(PermissionMode.YOLO); - System.out.println(permissionChangeResult.map(s -> s ? "setPermissionMode success" : "setPermissionMode error") - .orElse("setPermissionMode unknown")); +public class SetPermissionModeExample { + public static void main(String[] args) { + try (Session session = QwenCodeCli.newSession()) { + // Switch to a permissive mode + Optional 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()); + // 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 permissionChangeResult2 = session.setPermissionMode(PermissionMode.PLAN); - System.out.println(permissionChangeResult2.map(s -> s ? "setPermissionMode success" : "setPermissionMode error") - .orElse("setPermissionMode unknown")); + // Switch to another permission mode + Optional 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()); + // Use the session with the new permission mode + session.sendPrompt("rename test.touch to test_rename.touch", new SessionEventSimpleConsumers()); + } + } } ``` @@ -433,51 +474,59 @@ The timeout configuration allows you to control how long the SDK waits for respo To customize timeout settings: ```java -import com.alibaba.qwen.code.cli.utils.Timeout; -import java.util.concurrent.TimeUnit; +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; -// 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 +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); + 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 + // 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); } - - @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", sessionEventConsumers); +} ``` ### Thread Pool Configuration @@ -501,44 +550,56 @@ The thread pool can be customized in two ways: 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; -// Set a custom thread pool supplier -ThreadPoolConfig.setExecutorSupplier(new Supplier() { - @Override - public ThreadPoolExecutor get() { - return (ThreadPoolExecutor) Executors.newFixedThreadPool(20); - } -}); +public class ThreadPoolConfigurationExample { + public static void main(String[] args) { + // Set a custom thread pool supplier + ThreadPoolConfig.setExecutorSupplier(new Supplier() { + @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(); + // 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; -// Get the default executor and modify its properties -ThreadPoolExecutor executor = ThreadPoolConfig.getDefaultExecutor(); +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 core pool size + executor.setCorePoolSize(15); -// Modify the maximum pool size -executor.setMaximumPoolSize(40); + // Modify the maximum pool size + executor.setMaximumPoolSize(40); -// Modify the keep-alive time -executor.setKeepAliveTime(120, TimeUnit.SECONDS); + // 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(); + // 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. @@ -554,22 +615,31 @@ The SDK provides specific exception types for different error scenarios: 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; -try (Session session = QwenCodeCli.newSession()) { - try { - List 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(); +public class ErrorHandlingExample { + public static void main(String[] args) { + try (Session session = QwenCodeCli.newSession()) { + try { + List 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(); + } } -} 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(); } ``` diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 33de7f073..41b4b9c3a 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -3,10 +3,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 com.alibaba - qwencode-sdk-java + qwencode-sdk jar 0.0.1-SNAPSHOT - qwencode-sdk-java + qwencode-sdk https://maven.apache.org From 73848d3867d96c640210f714c128dfead232b7bd Mon Sep 17 00:00:00 2001 From: skyfire Date: Thu, 1 Jan 2026 01:30:58 +0800 Subject: [PATCH 36/65] fix arg --- packages/sdk-java/README.md | 108 ++++++++++++++++++ packages/sdk-java/pom.xml | 80 ++++++++++++- .../process/TransportOptionsAdapter.java | 2 +- 3 files changed, 188 insertions(+), 2 deletions(-) diff --git a/packages/sdk-java/README.md b/packages/sdk-java/README.md index 149d634c4..0898b6d31 100644 --- a/packages/sdk-java/README.md +++ b/packages/sdk-java/README.md @@ -115,6 +115,8 @@ The Qwen Code Java SDK follows a layered architecture that abstracts the communi 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: @@ -604,6 +606,112 @@ public class ModifyThreadPoolExample { 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` + - 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` + - 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` + - Example: `new TransportOptions().setExcludeTools(List.of("shell"))` + +- **`allowedTools`**: Specifies a list of tools that are pre-approved for use without additional confirmation. + - Type: `List` + - 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` + - 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 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: diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 41b4b9c3a..2bc86a988 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -5,7 +5,7 @@ com.alibaba qwencode-sdk jar - 0.0.1-SNAPSHOT + 0.0.1 qwencode-sdk https://maven.apache.org @@ -67,6 +67,34 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 1.8 + 1.8 + + + + compile-examples + compile + + compile + + + 1.8 + 1.8 + + com/alibaba/qwen/code/example/**/*.java + + + ${project.basedir}/src/example/java + + + + + org.apache.maven.plugins maven-checkstyle-plugin @@ -101,6 +129,56 @@ + + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 + true + + central + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java index a2226a499..ba9289181 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java @@ -42,7 +42,7 @@ class TransportOptionsAdapter { } if (transportOptions.getPermissionMode() != null) { - args.add("--permission-mode"); + args.add("--approval-mode"); args.add(transportOptions.getPermissionMode().getValue()); } From e5cced88135f4c569dd6cda9d54be4987e274d08 Mon Sep 17 00:00:00 2001 From: Weaxs <459312872@qq.com> Date: Fri, 2 Jan 2026 18:59:23 +0800 Subject: [PATCH 37/65] buildRequest add thinking config && convert Handle reasoning content --- .../core/openaiContentGenerator/converter.ts | 25 +++++++++++++++++++ .../core/openaiContentGenerator/pipeline.ts | 24 ++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/core/src/core/openaiContentGenerator/converter.ts b/packages/core/src/core/openaiContentGenerator/converter.ts index 07a8f1831..184ba5493 100644 --- a/packages/core/src/core/openaiContentGenerator/converter.ts +++ b/packages/core/src/core/openaiContentGenerator/converter.ts @@ -696,6 +696,17 @@ export class OpenAIContentConverter { parts.push({ text: choice.message.content }); } + // Handle reasoning content + const message = choice.message as typeof choice.message & { + reasoning_content?: string; + }; + if (message.reasoning_content) { + parts.push({ + text: message.reasoning_content, + thought: true, + } as unknown as Part); + } + // Handle tool calls if (choice.message.tool_calls) { for (const toolCall of choice.message.tool_calls) { @@ -752,6 +763,8 @@ export class OpenAIContentConverter { usage.prompt_tokens_details?.cached_tokens ?? extendedUsage.cached_tokens ?? 0; + const reasoningTokens = + usage.completion_tokens_details?.reasoning_tokens || 0; // If we only have total tokens but no breakdown, estimate the split // Typically input is ~70% and output is ~30% for most conversations @@ -769,6 +782,7 @@ export class OpenAIContentConverter { candidatesTokenCount: finalCompletionTokens, totalTokenCount: totalTokens, cachedContentTokenCount: cachedTokens, + thoughtsTokenCount: reasoningTokens, }; } @@ -800,6 +814,17 @@ export class OpenAIContentConverter { } } + // Handle reasoning content + const delta = choice.delta as typeof choice.delta & { + reasoning_content?: string; + }; + if (delta.reasoning_content) { + parts.push({ + text: delta.reasoning_content, + thought: true, + }); + } + // Handle tool calls using the streaming parser if (choice.delta?.tool_calls) { for (const toolCall of choice.delta.tool_calls) { diff --git a/packages/core/src/core/openaiContentGenerator/pipeline.ts b/packages/core/src/core/openaiContentGenerator/pipeline.ts index 88ac38f6a..0eab0c2d2 100644 --- a/packages/core/src/core/openaiContentGenerator/pipeline.ts +++ b/packages/core/src/core/openaiContentGenerator/pipeline.ts @@ -242,6 +242,30 @@ export class ContentGenerationPipeline { baseRequest.stream_options = { include_usage: true }; } + // Add thinking options if present + if ( + request.config?.thinkingConfig && + request.config.thinkingConfig.includeThoughts + ) { + ( + baseRequest as OpenAI.Chat.ChatCompletionCreateParams & { + extra_body?: Record; + } + ).extra_body = { enable_thinking: true }; + ( + baseRequest as OpenAI.Chat.ChatCompletionCreateParams & { + enable_thinking?: boolean; + } + ).enable_thinking = true; + if (request.config.thinkingConfig.thinkingBudget) { + ( + baseRequest as OpenAI.Chat.ChatCompletionCreateParams & { + thinking_budget?: number; + } + ).thinking_budget = request.config.thinkingConfig.thinkingBudget; + } + } + // Add tools if present if (request.config?.tools) { baseRequest.tools = await this.converter.convertGeminiToolsToOpenAI( From 473cb7b951dd9dc25bca258d204641a4b09b1eb7 Mon Sep 17 00:00:00 2001 From: liqoingyu Date: Sun, 4 Jan 2026 14:32:32 +0800 Subject: [PATCH 38/65] fix(cli): skip update check when disableUpdateNag is true --- packages/cli/src/gemini.test.tsx | 33 ++++++++++++++++++++++++++++++++ packages/cli/src/gemini.tsx | 22 +++++++++++---------- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/gemini.test.tsx b/packages/cli/src/gemini.test.tsx index 9fa0b8261..2d26877e6 100644 --- a/packages/cli/src/gemini.test.tsx +++ b/packages/cli/src/gemini.test.tsx @@ -639,4 +639,37 @@ describe('startInteractiveUI', () => { await new Promise((resolve) => setTimeout(resolve, 0)); expect(checkForUpdates).toHaveBeenCalledTimes(1); }); + + it('should not check for updates when update nag is disabled', async () => { + const { checkForUpdates } = await import('./ui/utils/updateCheck.js'); + + const mockInitializationResult = { + authError: null, + themeError: null, + shouldOpenAuthDialog: false, + geminiMdFileCount: 0, + }; + + const settingsWithUpdateNagDisabled = { + merged: { + general: { + disableUpdateNag: true, + }, + ui: { + hideWindowTitle: false, + }, + }, + } as LoadedSettings; + + await startInteractiveUI( + mockConfig, + settingsWithUpdateNagDisabled, + mockStartupWarnings, + mockWorkspaceRoot, + mockInitializationResult, + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(checkForUpdates).not.toHaveBeenCalled(); + }); }); diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index b05f12453..da945546d 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -183,16 +183,18 @@ export async function startInteractiveUI( }, ); - checkForUpdates() - .then((info) => { - handleAutoUpdate(info, settings, config.getProjectRoot()); - }) - .catch((err) => { - // Silently ignore update check errors. - if (config.getDebugMode()) { - console.error('Update check failed:', err); - } - }); + if (!settings.merged.general?.disableUpdateNag) { + checkForUpdates() + .then((info) => { + handleAutoUpdate(info, settings, config.getProjectRoot()); + }) + .catch((err) => { + // Silently ignore update check errors. + if (config.getDebugMode()) { + console.error('Update check failed:', err); + } + }); + } registerCleanup(() => instance.unmount()); } From db9d5cb45d32192121b1cec8af3d31e81dc3c36b Mon Sep 17 00:00:00 2001 From: skyfire Date: Sun, 4 Jan 2026 18:07:56 +0800 Subject: [PATCH 39/65] add javadoc --- packages/sdk-java/.gitignore | 1 + packages/sdk-java/README.md | 10 +- packages/sdk-java/pom.xml | 8 +- .../alibaba/qwen/code/cli/QwenCodeCli.java | 98 +++++-- .../cli/protocol/data/AssistantContent.java | 91 +++++- .../cli/protocol/data/AssistantUsage.java | 68 +++++ .../protocol/data/CLIPermissionDenial.java | 42 +++ .../code/cli/protocol/data/Capabilities.java | 68 +++++ .../code/cli/protocol/data/ExtendedUsage.java | 77 +++++ .../cli/protocol/data/InitializeConfig.java | 55 ++++ .../code/cli/protocol/data/ModelUsage.java | 81 ++++++ .../cli/protocol/data/PermissionMode.java | 26 ++ .../qwen/code/cli/protocol/data/Usage.java | 73 +++++ .../cli/protocol/data/behavior/Allow.java | 20 ++ .../cli/protocol/data/behavior/Behavior.java | 30 ++ .../code/cli/protocol/data/behavior/Deny.java | 20 ++ .../code/cli/protocol/message/Message.java | 15 + .../cli/protocol/message/MessageBase.java | 32 ++ .../protocol/message/SDKResultMessage.java | 178 ++++++++++++ .../protocol/message/SDKSystemMessage.java | 274 +++++++++++++++++- .../cli/protocol/message/SDKUserMessage.java | 105 ++++++- .../assistant/APIAssistantMessage.java | 101 ++++++- .../assistant/SDKAssistantMessage.java | 63 ++++ .../assistant/SDKPartialAssistantMessage.java | 58 ++++ .../message/assistant/block/Annotation.java | 30 +- .../message/assistant/block/ContentBlock.java | 47 ++- .../message/assistant/block/TextBlock.java | 21 +- .../assistant/block/ThinkingBlock.java | 34 ++- .../assistant/block/ToolResultBlock.java | 56 +++- .../message/assistant/block/ToolUseBlock.java | 67 ++++- .../event/ContentBlockDeltaEvent.java | 149 +++++++++- .../event/ContentBlockStartEvent.java | 9 + .../event/ContentBlockStopEvent.java | 16 + .../event/MessageStartStreamEvent.java | 59 +++- .../event/MessageStopStreamEvent.java | 3 + .../message/assistant/event/StreamEvent.java | 16 + .../control/CLIControlInitializeRequest.java | 30 ++ .../control/CLIControlInitializeResponse.java | 29 ++ .../control/CLIControlInterruptRequest.java | 16 + .../control/CLIControlPermissionRequest.java | 136 +++++++++ .../control/CLIControlPermissionResponse.java | 30 ++ .../message/control/CLIControlRequest.java | 43 +++ .../message/control/CLIControlResponse.java | 73 +++++ .../control/CLIControlSetModelRequest.java | 29 ++ .../control/CLIControlSetModelResponse.java | 29 ++ .../CLIControlSetPermissionModeRequest.java | 29 ++ .../qwen/code/cli/session/Session.java | 89 +++++- .../event/AssistantContentConsumers.java | 62 ++++ .../AssistantContentSimpleConsumers.java | 38 +++ .../session/event/SessionEventConsumers.java | 113 ++++++++ .../event/SessionEventSimpleConsumers.java | 111 ++++++- .../exception/SessionControlException.java | 30 ++ .../exception/SessionSendPromptException.java | 30 ++ .../qwen/code/cli/transport/Transport.java | 51 ++++ .../code/cli/transport/TransportOptions.java | 227 +++++++++++++++ .../transport/process/ProcessTransport.java | 21 ++ .../process/TransportOptionsAdapter.java | 38 +++ .../code/cli/utils/MyConcurrentUtils.java | 26 ++ .../qwen/code/cli/utils/ThreadPoolConfig.java | 14 + .../alibaba/qwen/code/cli/utils/Timeout.java | 32 ++ .../qwen/code/cli/QwenCodeCliTest.java | 11 +- .../qwen/code/cli/session/SessionTest.java | 48 ++- 62 files changed, 3395 insertions(+), 91 deletions(-) create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantUsage.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java diff --git a/packages/sdk-java/.gitignore b/packages/sdk-java/.gitignore index bb45e2790..23cdb8c94 100644 --- a/packages/sdk-java/.gitignore +++ b/packages/sdk-java/.gitignore @@ -11,3 +11,4 @@ log/ target/ +/docs/ diff --git a/packages/sdk-java/README.md b/packages/sdk-java/README.md index 0898b6d31..5794a9a16 100644 --- a/packages/sdk-java/README.md +++ b/packages/sdk-java/README.md @@ -150,8 +150,8 @@ 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; @@ -162,9 +162,9 @@ public class CustomEventHandlingExample { @Override public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) { String message = assistantMessage.getMessage().getContent().stream() - .findFirst() - .map(content -> content.getText()) - .orElse(""); + .findFirst() + .map(content -> content.getText()) + .orElse(""); System.out.println("Assistant: " + message); } @@ -213,7 +213,7 @@ public class CustomEventHandlingExample { public Behavior onPermissionRequest(Session session, CLIControlRequest 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 + .setUpdatedInput(permissionRequest.getRequest().getInput()); // Allow by default } @Override diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 2bc86a988..8786a78cc 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -5,7 +5,7 @@ com.alibaba qwencode-sdk jar - 0.0.1 + 0.0.1-SNAPSHOT qwencode-sdk https://maven.apache.org @@ -202,8 +202,10 @@ - central - https://central.sonatype.com/repository/maven-snapshots/ + + + snapshots + http://mvnrepo.alibaba-inc.com/mvn/snapshots diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java index 1e306dc15..adbb235e6 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java @@ -2,13 +2,17 @@ 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.AssistantUsage; import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation; import com.alibaba.qwen.code.cli.session.Session; +import com.alibaba.qwen.code.cli.session.event.AssistantContentConsumers; 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; @@ -19,32 +23,77 @@ import com.alibaba.qwen.code.cli.utils.Timeout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Main entry point for interacting with the Qwen Code CLI. Provides static methods for simple queries and session management. + */ public class QwenCodeCli { private static final Logger log = LoggerFactory.getLogger(QwenCodeCli.class); + /** + * Sends a simple query to the Qwen Code CLI and returns a list of responses. + * + * @param prompt The input prompt to send to the CLI + * @return A list of strings representing the CLI's responses + */ public static List simpleQuery(String prompt) { + return simpleQuery(prompt, new TransportOptions()); + } + + /** + * Sends a simple query with custom transport options. + * + * @param prompt The input prompt to send to the CLI + * @param transportOptions Configuration options for the transport layer + * @return A list of strings representing the CLI's responses + */ + public static List simpleQuery(String prompt, TransportOptions transportOptions) { final List response = new ArrayList<>(); - MyConcurrentUtils.runAndWait(() -> simpleQuery(prompt, response::add), Timeout.TIMEOUT_30_MINUTES); + MyConcurrentUtils.runAndWait(() -> simpleQuery(prompt, transportOptions, new AssistantContentConsumers() { + @Override + public void onText(Session session, TextAssistantContent textAssistantContent) { + response.add(textAssistantContent.getText()); + } + + @Override + public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { + response.add(thingkingAssistantContent.getThinking()); + } + + @Override + public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) { + response.add(JSON.toJSONString(toolUseAssistantContent.getContentOfAssistant())); + } + + @Override + public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) { + response.add(JSON.toJSONString(toolResultAssistantContent)); + } + + public void onOtherContent(Session session, AssistantContent other) { + response.add(JSON.toJSONString(other.getContentOfAssistant())); + } + + @Override + public void onUsage(Session session, AssistantUsage assistantUsage) { + log.info("received usage {} of message {}", assistantUsage.getUsage(), assistantUsage.getMessageId()); + } + }), Timeout.TIMEOUT_30_MINUTES); return response; } - public static void simpleQuery(String prompt, Consumer messageConsumer) { - Session session = newSession(new TransportOptions()); + /** + * Sends a query with custom content consumers. + * + * @param prompt The input prompt to send to the CLI + * @param transportOptions Configuration options for the transport layer + * @param assistantContentConsumers Consumers for handling different types of assistant content + */ + public static void simpleQuery(String prompt, TransportOptions transportOptions, AssistantContentConsumers assistantContentConsumers) { + Session session = newSession(transportOptions); try { - session.sendPrompt(prompt, new SessionEventSimpleConsumers() { - @Override - public void onAssistantMessageIncludePartial(Session session, List 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)); + session.sendPrompt(prompt, new SessionEventSimpleConsumers() + .setDefaultPermissionOperation(Operation.allow) + .setBlockConsumer(assistantContentConsumers)); } catch (Exception e) { throw new RuntimeException("sendPrompt error!", e); } finally { @@ -56,10 +105,21 @@ public class QwenCodeCli { } } + /** + * Creates a new session with default transport options. + * + * @return A new Session instance + */ public static Session newSession() { return newSession(new TransportOptions()); } + /** + * Creates a new session with custom transport options. + * + * @param transportOptions Configuration options for the transport layer + * @return A new Session instance + */ public static Session newSession(TransportOptions transportOptions) { Transport transport; try { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java index 40d7f520d..d0414a83f 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java @@ -1,6 +1,93 @@ package com.alibaba.qwen.code.cli.protocol.data; -public interface AssistantContent { +import java.util.Map; + +/** + * Represents content from the assistant in a Qwen Code session. + * + * @param The type of content + */ +public interface AssistantContent { + /** + * Gets the type of the assistant content. + * + * @return The type of the assistant content + */ String getType(); - Object getContent(); + + /** + * Gets the actual content from the assistant. + * + * @return The content from the assistant + */ + C getContentOfAssistant(); + + /** + * Gets the message ID associated with this content. + * + * @return The message ID + */ + String getMessageId(); + + /** + * Represents text content from the assistant. + */ + interface TextAssistantContent extends AssistantContent { + /** + * Gets the text content. + * + * @return The text content + */ + String getText(); + } + + /** + * Represents thinking content from the assistant. + */ + interface ThingkingAssistantContent extends AssistantContent { + /** + * Gets the thinking content. + * + * @return The thinking content + */ + String getThinking(); + } + + /** + * Represents tool use content from the assistant. + */ + interface ToolUseAssistantContent extends AssistantContent> { + /** + * Gets the tool input. + * + * @return The tool input + */ + Map getInput(); + } + + /** + * Represents tool result content from the assistant. + */ + interface ToolResultAssistantContent extends AssistantContent { + /** + * Gets whether the tool result indicates an error. + * + * @return Whether the tool result indicates an error + */ + Boolean getIsError(); + + /** + * Gets the tool result content. + * + * @return The tool result content + */ + String getContent(); + + /** + * Gets the tool use ID. + * + * @return The tool use ID + */ + String getToolUseId(); + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantUsage.java new file mode 100644 index 000000000..261898796 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantUsage.java @@ -0,0 +1,68 @@ +package com.alibaba.qwen.code.cli.protocol.data; + +import com.alibaba.fastjson2.JSON; + +/** + * Represents usage information for an assistant message. + */ +public class AssistantUsage { + /** + * The ID of the message. + */ + String messageId; + /** + * The usage information. + */ + Usage usage; + + /** + * Gets the message ID. + * + * @return The message ID + */ + public String getMessageId() { + return messageId; + } + + /** + * Sets the message ID. + * + * @param messageId The message ID + */ + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + /** + * Gets the usage information. + * + * @return The usage information + */ + public Usage getUsage() { + return usage; + } + + /** + * Sets the usage information. + * + * @param usage The usage information + */ + public void setUsage(Usage usage) { + this.usage = usage; + } + + /** + * Constructs a new AssistantUsage instance. + * + * @param messageId The message ID + * @param usage The usage information + */ + public AssistantUsage(String messageId, Usage usage) { + this.messageId = messageId; + this.usage = usage; + } + + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java index 4abd68bc3..312344296 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java @@ -2,36 +2,78 @@ package com.alibaba.qwen.code.cli.protocol.data; import com.alibaba.fastjson2.annotation.JSONField; +/** + * Represents a permission denial from the CLI. + */ public class CLIPermissionDenial { + /** + * The name of the denied tool. + */ @JSONField(name = "tool_name") private String toolName; + /** + * The ID of the denied tool use. + */ @JSONField(name = "tool_use_id") private String toolUseId; + /** + * The input for the denied tool. + */ @JSONField(name = "tool_input") private Object toolInput; + /** + * Gets the name of the denied tool. + * + * @return The name of the denied tool + */ public String getToolName() { return toolName; } + /** + * Sets the name of the denied tool. + * + * @param toolName The name of the denied tool + */ public void setToolName(String toolName) { this.toolName = toolName; } + /** + * Gets the ID of the denied tool use. + * + * @return The ID of the denied tool use + */ public String getToolUseId() { return toolUseId; } + /** + * Sets the ID of the denied tool use. + * + * @param toolUseId The ID of the denied tool use + */ public void setToolUseId(String toolUseId) { this.toolUseId = toolUseId; } + /** + * Gets the input for the denied tool. + * + * @return The input for the denied tool + */ public Object getToolInput() { return toolInput; } + /** + * Sets the input for the denied tool. + * + * @param toolInput The input for the denied tool + */ public void setToolInput(Object toolInput) { this.toolInput = toolInput; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java index 13200b654..2f22c0ce4 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java @@ -2,58 +2,126 @@ package com.alibaba.qwen.code.cli.protocol.data; import com.alibaba.fastjson2.annotation.JSONField; +/** + * Represents the capabilities of the Qwen Code CLI. + */ public class Capabilities { + /** + * Whether the CLI can handle can_use_tool requests. + */ @JSONField(name = "can_handle_can_use_tool") boolean canHandleCanUseTool; + /** + * Whether the CLI can handle hook callbacks. + */ @JSONField(name = "can_handle_hook_callback") boolean canHandleHookCallback; + /** + * Whether the CLI can set permission mode. + */ @JSONField(name = "can_set_permission_mode") boolean canSetPermissionMode; + /** + * Whether the CLI can set the model. + */ @JSONField(name = "can_set_model") boolean canSetModel; + /** + * Whether the CLI can handle MCP messages. + */ @JSONField(name = "can_handle_mcp_message") boolean canHandleMcpMessage; + /** + * Checks if the CLI can handle can_use_tool requests. + * + * @return true if the CLI can handle can_use_tool requests, false otherwise + */ public boolean isCanHandleCanUseTool() { return canHandleCanUseTool; } + /** + * Sets whether the CLI can handle can_use_tool requests. + * + * @param canHandleCanUseTool Whether the CLI can handle can_use_tool requests + */ public void setCanHandleCanUseTool(boolean canHandleCanUseTool) { this.canHandleCanUseTool = canHandleCanUseTool; } + /** + * Checks if the CLI can handle hook callbacks. + * + * @return true if the CLI can handle hook callbacks, false otherwise + */ public boolean isCanHandleHookCallback() { return canHandleHookCallback; } + /** + * Sets whether the CLI can handle hook callbacks. + * + * @param canHandleHookCallback Whether the CLI can handle hook callbacks + */ public void setCanHandleHookCallback(boolean canHandleHookCallback) { this.canHandleHookCallback = canHandleHookCallback; } + /** + * Checks if the CLI can set permission mode. + * + * @return true if the CLI can set permission mode, false otherwise + */ public boolean isCanSetPermissionMode() { return canSetPermissionMode; } + /** + * Sets whether the CLI can set permission mode. + * + * @param canSetPermissionMode Whether the CLI can set permission mode + */ public void setCanSetPermissionMode(boolean canSetPermissionMode) { this.canSetPermissionMode = canSetPermissionMode; } + /** + * Checks if the CLI can set the model. + * + * @return true if the CLI can set the model, false otherwise + */ public boolean isCanSetModel() { return canSetModel; } + /** + * Sets whether the CLI can set the model. + * + * @param canSetModel Whether the CLI can set the model + */ public void setCanSetModel(boolean canSetModel) { this.canSetModel = canSetModel; } + /** + * Checks if the CLI can handle MCP messages. + * + * @return true if the CLI can handle MCP messages, false otherwise + */ public boolean isCanHandleMcpMessage() { return canHandleMcpMessage; } + /** + * Sets whether the CLI can handle MCP messages. + * + * @param canHandleMcpMessage Whether the CLI can handle MCP messages + */ public void setCanHandleMcpMessage(boolean canHandleMcpMessage) { this.canHandleMcpMessage = canHandleMcpMessage; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java index 4965f4b8c..a894f5b7a 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java @@ -2,64 +2,141 @@ package com.alibaba.qwen.code.cli.protocol.data; import com.alibaba.fastjson2.annotation.JSONField; +/** + * Extends the Usage class with additional usage information. + */ public class ExtendedUsage extends Usage { + /** + * Server tool use information. + */ @JSONField(name = "server_tool_use") private ServerToolUse serverToolUse; + /** + * Service tier information. + */ @JSONField(name = "service_tier") private String serviceTier; + /** + * Cache creation information. + */ @JSONField(name = "cache_creation") private CacheCreation cacheCreation; + /** + * Gets the server tool use information. + * + * @return The server tool use information + */ public ServerToolUse getServerToolUse() { return serverToolUse; } + /** + * Sets the server tool use information. + * + * @param serverToolUse The server tool use information + */ public void setServerToolUse(ServerToolUse serverToolUse) { this.serverToolUse = serverToolUse; } + /** + * Gets the service tier information. + * + * @return The service tier information + */ public String getServiceTier() { return serviceTier; } + /** + * Sets the service tier information. + * + * @param serviceTier The service tier information + */ public void setServiceTier(String serviceTier) { this.serviceTier = serviceTier; } + /** + * Gets the cache creation information. + * + * @return The cache creation information + */ public CacheCreation getCacheCreation() { return cacheCreation; } + /** + * Sets the cache creation information. + * + * @param cacheCreation The cache creation information + */ public void setCacheCreation(CacheCreation cacheCreation) { this.cacheCreation = cacheCreation; } + /** + * Represents server tool use information. + */ public static class ServerToolUse { + /** + * Number of web search requests. + */ @JSONField(name = "web_search_requests") private int webSearchRequests; } + /** + * Represents cache creation information. + */ public static class CacheCreation { + /** + * Number of ephemeral 1-hour input tokens. + */ @JSONField(name = "ephemeral_1h_input_tokens") private int ephemeral1hInputTokens; + /** + * Number of ephemeral 5-minute input tokens. + */ @JSONField(name = "ephemeral_5m_input_tokens") private int ephemeral5mInputTokens; + /** + * Gets the number of ephemeral 1-hour input tokens. + * + * @return The number of ephemeral 1-hour input tokens + */ public int getEphemeral1hInputTokens() { return ephemeral1hInputTokens; } + /** + * Sets the number of ephemeral 1-hour input tokens. + * + * @param ephemeral1hInputTokens The number of ephemeral 1-hour input tokens + */ public void setEphemeral1hInputTokens(int ephemeral1hInputTokens) { this.ephemeral1hInputTokens = ephemeral1hInputTokens; } + /** + * Gets the number of ephemeral 5-minute input tokens. + * + * @return The number of ephemeral 5-minute input tokens + */ public int getEphemeral5mInputTokens() { return ephemeral5mInputTokens; } + /** + * Sets the number of ephemeral 5-minute input tokens. + * + * @param ephemeral5mInputTokens The number of ephemeral 5-minute input tokens + */ public void setEphemeral5mInputTokens(int ephemeral5mInputTokens) { this.ephemeral5mInputTokens = ephemeral5mInputTokens; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java index ccafed4f0..c0858ee4d 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java @@ -1,39 +1,94 @@ package com.alibaba.qwen.code.cli.protocol.data; +/** + * Configuration for initializing the CLI. + */ public class InitializeConfig { + /** + * Hooks configuration. + */ String hooks; + /** + * SDK MCP servers configuration. + */ String sdkMcpServers; + /** + * MCP servers configuration. + */ String mcpServers; + /** + * Agents configuration. + */ String agents; + /** + * Gets the hooks configuration. + * + * @return The hooks configuration + */ public String getHooks() { return hooks; } + /** + * Sets the hooks configuration. + * + * @param hooks The hooks configuration + */ public void setHooks(String hooks) { this.hooks = hooks; } + /** + * Gets the SDK MCP servers configuration. + * + * @return The SDK MCP servers configuration + */ public String getSdkMcpServers() { return sdkMcpServers; } + /** + * Sets the SDK MCP servers configuration. + * + * @param sdkMcpServers The SDK MCP servers configuration + */ public void setSdkMcpServers(String sdkMcpServers) { this.sdkMcpServers = sdkMcpServers; } + /** + * Gets the MCP servers configuration. + * + * @return The MCP servers configuration + */ public String getMcpServers() { return mcpServers; } + /** + * Sets the MCP servers configuration. + * + * @param mcpServers The MCP servers configuration + */ public void setMcpServers(String mcpServers) { this.mcpServers = mcpServers; } + /** + * Gets the agents configuration. + * + * @return The agents configuration + */ public String getAgents() { return agents; } + /** + * Sets the agents configuration. + * + * @param agents The agents configuration + */ public void setAgents(String agents) { this.agents = agents; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java index 22787f232..d3286c8a3 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java @@ -1,57 +1,138 @@ package com.alibaba.qwen.code.cli.protocol.data; +/** + * Represents usage information for a specific model. + */ public class ModelUsage { + /** + * Number of input tokens. + */ private int inputTokens; + /** + * Number of output tokens. + */ private int outputTokens; + /** + * Number of cache read input tokens. + */ private int cacheReadInputTokens; + /** + * Number of cache creation input tokens. + */ private int cacheCreationInputTokens; + /** + * Number of web search requests. + */ private int webSearchRequests; + /** + * Context window size. + */ private int contextWindow; + /** + * Gets the number of input tokens. + * + * @return The number of input tokens + */ public int getInputTokens() { return inputTokens; } + /** + * Sets the number of input tokens. + * + * @param inputTokens The number of input tokens + */ public void setInputTokens(int inputTokens) { this.inputTokens = inputTokens; } + /** + * Gets the number of output tokens. + * + * @return The number of output tokens + */ public int getOutputTokens() { return outputTokens; } + /** + * Sets the number of output tokens. + * + * @param outputTokens The number of output tokens + */ public void setOutputTokens(int outputTokens) { this.outputTokens = outputTokens; } + /** + * Gets the number of cache read input tokens. + * + * @return The number of cache read input tokens + */ public int getCacheReadInputTokens() { return cacheReadInputTokens; } + /** + * Sets the number of cache read input tokens. + * + * @param cacheReadInputTokens The number of cache read input tokens + */ public void setCacheReadInputTokens(int cacheReadInputTokens) { this.cacheReadInputTokens = cacheReadInputTokens; } + /** + * Gets the number of cache creation input tokens. + * + * @return The number of cache creation input tokens + */ public int getCacheCreationInputTokens() { return cacheCreationInputTokens; } + /** + * Sets the number of cache creation input tokens. + * + * @param cacheCreationInputTokens The number of cache creation input tokens + */ public void setCacheCreationInputTokens(int cacheCreationInputTokens) { this.cacheCreationInputTokens = cacheCreationInputTokens; } + /** + * Gets the number of web search requests. + * + * @return The number of web search requests + */ public int getWebSearchRequests() { return webSearchRequests; } + /** + * Sets the number of web search requests. + * + * @param webSearchRequests The number of web search requests + */ public void setWebSearchRequests(int webSearchRequests) { this.webSearchRequests = webSearchRequests; } + /** + * Gets the context window size. + * + * @return The context window size + */ public int getContextWindow() { return contextWindow; } + /** + * Sets the context window size. + * + * @param contextWindow The context window size + */ public void setContextWindow(int contextWindow) { this.contextWindow = contextWindow; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java index d960a396e..aafc69bef 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java @@ -1,9 +1,24 @@ package com.alibaba.qwen.code.cli.protocol.data; +/** + * Represents different permission modes for the CLI. + */ public enum PermissionMode { + /** + * Default permission mode. + */ DEFAULT("default"), + /** + * Plan permission mode. + */ PLAN("plan"), + /** + * Auto-edit permission mode. + */ AUTO_EDIT("auto-edit"), + /** + * YOLO permission mode. + */ YOLO("yolo"); private final String value; @@ -12,10 +27,21 @@ public enum PermissionMode { this.value = value; } + /** + * Gets the string value of the permission mode. + * + * @return The string value of the permission mode + */ public String getValue() { return value; } + /** + * Gets the permission mode from its string value. + * + * @param value The string value + * @return The corresponding permission mode + */ public static PermissionMode fromValue(String value) { for (PermissionMode mode : PermissionMode.values()) { if (mode.value.equals(value)) { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java index 1222b16f2..a0e4b3009 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java @@ -1,56 +1,129 @@ package com.alibaba.qwen.code.cli.protocol.data; +import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.annotation.JSONField; +/** + * Represents usage information for a message. + */ public class Usage { + /** + * Number of input tokens. + */ @JSONField(name = "input_tokens") private Integer inputTokens; + /** + * Number of output tokens. + */ @JSONField(name = "output_tokens") private Integer outputTokens; + /** + * Number of cache creation input tokens. + */ @JSONField(name = "cache_creation_input_tokens") private Integer cacheCreationInputTokens; + /** + * Number of cache read input tokens. + */ @JSONField(name = "cache_read_input_tokens") private Integer cacheReadInputTokens; + /** + * Total number of tokens. + */ @JSONField(name = "total_tokens") private Integer totalTokens; + /** + * Gets the number of input tokens. + * + * @return The number of input tokens + */ public Integer getInputTokens() { return inputTokens; } + /** + * Sets the number of input tokens. + * + * @param inputTokens The number of input tokens + */ public void setInputTokens(Integer inputTokens) { this.inputTokens = inputTokens; } + /** + * Gets the number of output tokens. + * + * @return The number of output tokens + */ public Integer getOutputTokens() { return outputTokens; } + /** + * Sets the number of output tokens. + * + * @param outputTokens The number of output tokens + */ public void setOutputTokens(Integer outputTokens) { this.outputTokens = outputTokens; } + /** + * Gets the number of cache creation input tokens. + * + * @return The number of cache creation input tokens + */ public Integer getCacheCreationInputTokens() { return cacheCreationInputTokens; } + /** + * Sets the number of cache creation input tokens. + * + * @param cacheCreationInputTokens The number of cache creation input tokens + */ public void setCacheCreationInputTokens(Integer cacheCreationInputTokens) { this.cacheCreationInputTokens = cacheCreationInputTokens; } + /** + * Gets the number of cache read input tokens. + * + * @return The number of cache read input tokens + */ public Integer getCacheReadInputTokens() { return cacheReadInputTokens; } + /** + * Sets the number of cache read input tokens. + * + * @param cacheReadInputTokens The number of cache read input tokens + */ public void setCacheReadInputTokens(Integer cacheReadInputTokens) { this.cacheReadInputTokens = cacheReadInputTokens; } + /** + * Gets the total number of tokens. + * + * @return The total number of tokens + */ public Integer getTotalTokens() { return totalTokens; } + /** + * Sets the total number of tokens. + * + * @param totalTokens The total number of tokens + */ public void setTotalTokens(Integer totalTokens) { this.totalTokens = totalTokens; } + + public String toString() { + return JSON.toJSONString(this); + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java index 14adf7a2f..cc2f5d533 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java @@ -4,18 +4,38 @@ import java.util.Map; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Represents an allow behavior that permits an operation. + */ @JSONType(typeKey = "operation", typeName = "allow") public class Allow extends Behavior { + /** + * Creates a new Allow instance and sets the behavior to allow. + */ public Allow() { super(); this.behavior = Operation.allow; } + /** + * Updated input for the operation. + */ Map updatedInput; + /** + * Gets the updated input. + * + * @return The updated input + */ public Map getUpdatedInput() { return updatedInput; } + /** + * Sets the updated input. + * + * @param updatedInput The updated input + * @return This instance for method chaining + */ public Allow setUpdatedInput(Map updatedInput) { this.updatedInput = updatedInput; return this; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java index 1f54f2341..2ea1b6ff1 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java @@ -2,23 +2,53 @@ package com.alibaba.qwen.code.cli.protocol.data.behavior; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Base class for behavior objects that define how the CLI should handle requests. + */ @JSONType(typeKey = "operation", typeName = "Behavior", seeAlso = {Allow.class, Deny.class}) public class Behavior { + /** + * The behavior operation (allow or deny). + */ Operation behavior; + /** + * Gets the behavior operation. + * + * @return The behavior operation + */ public Operation getBehavior() { return behavior; } + /** + * Sets the behavior operation. + * + * @param behavior The behavior operation + */ public void setBehavior(Operation behavior) { this.behavior = behavior; } + /** + * Represents the type of operation. + */ public enum Operation { + /** + * Allow the operation. + */ allow, + /** + * Deny the operation. + */ deny } + /** + * Gets the default behavior (deny with message). + * + * @return The default behavior + */ public static Behavior defaultBehavior() { return new Deny().setMessage("Default Behavior Permission denied"); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java index 17d37ca05..d24560620 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java @@ -2,19 +2,39 @@ package com.alibaba.qwen.code.cli.protocol.data.behavior; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Represents a deny behavior that rejects an operation. + */ @JSONType(typeKey = "operation", typeName = "deny") public class Deny extends Behavior { + /** + * Creates a new Deny instance and sets the behavior to deny. + */ public Deny() { super(); this.behavior = Operation.deny; } + /** + * The message explaining why the operation was denied. + */ String message; + /** + * Gets the denial message. + * + * @return The denial message + */ public String getMessage() { return message; } + /** + * Sets the denial message. + * + * @param message The denial message + * @return This instance for method chaining + */ public Deny setMessage(String message) { this.message = message; return this; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java index de43924df..f816d7f2e 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java @@ -1,5 +1,20 @@ package com.alibaba.qwen.code.cli.protocol.message; +/** + * Represents a message in the Qwen Code protocol. + */ public interface Message { + /** + * Gets the type of the message. + * + * @return The type of the message + */ String getType(); + + /** + * Gets the ID of the message. + * + * @return The ID of the message + */ + String getMessageId(); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java index c66df12c4..aa9cbfd1a 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java @@ -1,12 +1,25 @@ package com.alibaba.qwen.code.cli.protocol.message; import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Base class for messages in the Qwen Code protocol. + */ @JSONType(alphabetic = false, typeKey = "type", typeName = "MessageBase") public class MessageBase implements Message{ + /** + * The type of the message. + */ protected String type; + /** + * The ID of the message. + */ + @JSONField(name = "message_id") + protected String messageId; + public String toString() { return JSON.toJSONString(this); } @@ -16,7 +29,26 @@ public class MessageBase implements Message{ return type; } + /** + * Sets the type of the message. + * + * @param type The type of the message + */ public void setType(String type) { this.type = type; } + + @Override + public String getMessageId() { + return messageId; + } + + /** + * Sets the ID of the message. + * + * @param messageId The ID of the message + */ + public void setMessageId(String messageId) { + this.messageId = messageId; + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java index dfa2275ff..f96ecade7 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java @@ -9,141 +9,319 @@ import com.alibaba.qwen.code.cli.protocol.data.CLIPermissionDenial; import com.alibaba.qwen.code.cli.protocol.data.ExtendedUsage; import com.alibaba.qwen.code.cli.protocol.data.Usage; +/** + * Represents a result message from the SDK. + */ @JSONType(typeKey = "type", typeName = "result") public class SDKResultMessage extends MessageBase { + /** + * The subtype of the result. + */ private String subtype; // 'error_max_turns' | 'error_during_execution' + /** + * The UUID of the message. + */ private String uuid; + /** + * The session ID. + */ @JSONField(name = "session_id") private String sessionId; + /** + * Whether the result represents an error. + */ @JSONField(name = "is_error") private boolean isError = true; + /** + * Duration in milliseconds. + */ @JSONField(name = "duration_ms") private Long durationMs; + /** + * API duration in milliseconds. + */ @JSONField(name = "duration_api_ms") private Long durationApiMs; + /** + * Number of turns. + */ @JSONField(name = "num_turns") private Integer numTurns; + /** + * Usage information. + */ private ExtendedUsage usage; + /** + * Model usage information. + */ private Map modelUsage; + /** + * List of permission denials. + */ @JSONField(name = "permission_denials") private List permissionDenials; + /** + * Error information. + */ private Error error; + /** + * Creates a new SDKResultMessage instance and sets the type to "result". + */ public SDKResultMessage() { super(); this.type = "result"; } + /** + * Gets the subtype of the result. + * + * @return The subtype of the result + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the result. + * + * @param subtype The subtype of the result + */ public void setSubtype(String subtype) { this.subtype = subtype; } + /** + * Gets the UUID of the message. + * + * @return The UUID of the message + */ public String getUuid() { return uuid; } + /** + * Sets the UUID of the message. + * + * @param uuid The UUID of the message + */ public void setUuid(String uuid) { this.uuid = uuid; } + /** + * Gets the session ID. + * + * @return The session ID + */ public String getSessionId() { return sessionId; } + /** + * Sets the session ID. + * + * @param sessionId The session ID + */ public void setSessionId(String sessionId) { this.sessionId = sessionId; } + /** + * Checks if the result represents an error. + * + * @return Whether the result represents an error + */ public boolean isError() { return isError; } + /** + * Sets whether the result represents an error. + * + * @param error Whether the result represents an error + */ public void setError(boolean error) { isError = error; } + /** + * Gets the duration in milliseconds. + * + * @return The duration in milliseconds + */ public Long getDurationMs() { return durationMs; } + /** + * Sets the duration in milliseconds. + * + * @param durationMs The duration in milliseconds + */ public void setDurationMs(Long durationMs) { this.durationMs = durationMs; } + /** + * Gets the API duration in milliseconds. + * + * @return The API duration in milliseconds + */ public Long getDurationApiMs() { return durationApiMs; } + /** + * Sets the API duration in milliseconds. + * + * @param durationApiMs The API duration in milliseconds + */ public void setDurationApiMs(Long durationApiMs) { this.durationApiMs = durationApiMs; } + /** + * Gets the number of turns. + * + * @return The number of turns + */ public Integer getNumTurns() { return numTurns; } + /** + * Sets the number of turns. + * + * @param numTurns The number of turns + */ public void setNumTurns(Integer numTurns) { this.numTurns = numTurns; } + /** + * Gets the usage information. + * + * @return The usage information + */ public ExtendedUsage getUsage() { return usage; } + /** + * Sets the usage information. + * + * @param usage The usage information + */ public void setUsage(ExtendedUsage usage) { this.usage = usage; } + /** + * Gets the model usage information. + * + * @return The model usage information + */ public Map getModelUsage() { return modelUsage; } + /** + * Sets the model usage information. + * + * @param modelUsage The model usage information + */ public void setModelUsage(Map modelUsage) { this.modelUsage = modelUsage; } + /** + * Gets the list of permission denials. + * + * @return The list of permission denials + */ public List getPermissionDenials() { return permissionDenials; } + /** + * Sets the list of permission denials. + * + * @param permissionDenials The list of permission denials + */ public void setPermissionDenials(List permissionDenials) { this.permissionDenials = permissionDenials; } + /** + * Gets the error information. + * + * @return The error information + */ public Error getError() { return error; } + /** + * Sets the error information. + * + * @param error The error information + */ public void setError(Error error) { this.error = error; } + /** + * Represents error information. + */ public static class Error { + /** + * Error type. + */ private String type; + /** + * Error message. + */ private String message; + /** + * Gets the error type. + * + * @return The error type + */ public String getType() { return type; } + /** + * Sets the error type. + * + * @param type The error type + */ public void setType(String type) { this.type = type; } + /** + * Gets the error message. + * + * @return The error message + */ public String getMessage() { return message; } + /** + * Sets the error message. + * + * @param message The error message + */ public void setMessage(String message) { this.message = message; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java index 22870cb85..4a61513d5 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java @@ -6,206 +6,476 @@ import java.util.Map; import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Represents a system message from the SDK. + */ @JSONType(typeKey = "type", typeName = "system") public class SDKSystemMessage extends MessageBase { + /** + * The subtype of the system message. + */ private String subtype; + /** + * The UUID of the message. + */ private String uuid; + /** + * The session ID. + */ @JSONField(name = "session_id") private String sessionId; + /** + * Additional data. + */ private Object data; + /** + * Current working directory. + */ private String cwd; + /** + * List of available tools. + */ private List tools; + /** + * List of MCP servers. + */ @JSONField(name = "mcp_servers") private List mcpServers; + /** + * Model information. + */ private String model; + /** + * Permission mode. + */ @JSONField(name = "permission_mode") private String permissionMode; + /** + * Available slash commands. + */ @JSONField(name = "slash_commands") private List slashCommands; + /** + * Qwen Code version. + */ @JSONField(name = "qwen_code_version") private String qwenCodeVersion; + /** + * Output style. + */ @JSONField(name = "output_style") private String outputStyle; + /** + * Available agents. + */ private List agents; + /** + * Available skills. + */ private List skills; + /** + * Capabilities information. + */ private Map capabilities; + /** + * Compact metadata. + */ @JSONField(name = "compact_metadata") private CompactMetadata compactMetadata; + /** + * Creates a new SDKSystemMessage instance and sets the type to "system". + */ public SDKSystemMessage() { super(); this.type = "system"; } + /** + * Gets the subtype of the system message. + * + * @return The subtype of the system message + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the system message. + * + * @param subtype The subtype of the system message + */ public void setSubtype(String subtype) { this.subtype = subtype; } + /** + * Gets the UUID of the message. + * + * @return The UUID of the message + */ public String getUuid() { return uuid; } + /** + * Sets the UUID of the message. + * + * @param uuid The UUID of the message + */ public void setUuid(String uuid) { this.uuid = uuid; } + /** + * Gets the session ID. + * + * @return The session ID + */ public String getSessionId() { return sessionId; } + /** + * Sets the session ID. + * + * @param sessionId The session ID + */ public void setSessionId(String sessionId) { this.sessionId = sessionId; } + /** + * Gets the additional data. + * + * @return The additional data + */ public Object getData() { return data; } + /** + * Sets the additional data. + * + * @param data The additional data + */ public void setData(Object data) { this.data = data; } + /** + * Gets the current working directory. + * + * @return The current working directory + */ public String getCwd() { return cwd; } + /** + * Sets the current working directory. + * + * @param cwd The current working directory + */ public void setCwd(String cwd) { this.cwd = cwd; } + /** + * Gets the list of available tools. + * + * @return The list of available tools + */ public List getTools() { return tools; } + /** + * Sets the list of available tools. + * + * @param tools The list of available tools + */ public void setTools(List tools) { this.tools = tools; } + /** + * Gets the list of MCP servers. + * + * @return The list of MCP servers + */ public List getMcpServers() { return mcpServers; } + /** + * Sets the list of MCP servers. + * + * @param mcpServers The list of MCP servers + */ public void setMcpServers(List mcpServers) { this.mcpServers = mcpServers; } + /** + * Gets the model information. + * + * @return The model information + */ public String getModel() { return model; } + /** + * Sets the model information. + * + * @param model The model information + */ public void setModel(String model) { this.model = model; } + /** + * Gets the permission mode. + * + * @return The permission mode + */ public String getPermissionMode() { return permissionMode; } + /** + * Sets the permission mode. + * + * @param permissionMode The permission mode + */ public void setPermissionMode(String permissionMode) { this.permissionMode = permissionMode; } + /** + * Gets the available slash commands. + * + * @return The available slash commands + */ public List getSlashCommands() { return slashCommands; } + /** + * Sets the available slash commands. + * + * @param slashCommands The available slash commands + */ public void setSlashCommands(List slashCommands) { this.slashCommands = slashCommands; } + /** + * Gets the Qwen Code version. + * + * @return The Qwen Code version + */ public String getQwenCodeVersion() { return qwenCodeVersion; } + /** + * Sets the Qwen Code version. + * + * @param qwenCodeVersion The Qwen Code version + */ public void setQwenCodeVersion(String qwenCodeVersion) { this.qwenCodeVersion = qwenCodeVersion; } + /** + * Gets the output style. + * + * @return The output style + */ public String getOutputStyle() { return outputStyle; } + /** + * Sets the output style. + * + * @param outputStyle The output style + */ public void setOutputStyle(String outputStyle) { this.outputStyle = outputStyle; } + /** + * Gets the available agents. + * + * @return The available agents + */ public List getAgents() { return agents; } + /** + * Sets the available agents. + * + * @param agents The available agents + */ public void setAgents(List agents) { this.agents = agents; } + /** + * Gets the available skills. + * + * @return The available skills + */ public List getSkills() { return skills; } + /** + * Sets the available skills. + * + * @param skills The available skills + */ public void setSkills(List skills) { this.skills = skills; } + /** + * Gets the capabilities information. + * + * @return The capabilities information + */ public Map getCapabilities() { return capabilities; } + /** + * Sets the capabilities information. + * + * @param capabilities The capabilities information + */ public void setCapabilities(Map capabilities) { this.capabilities = capabilities; } + /** + * Gets the compact metadata. + * + * @return The compact metadata + */ public CompactMetadata getCompactMetadata() { return compactMetadata; } + /** + * Sets the compact metadata. + * + * @param compactMetadata The compact metadata + */ public void setCompactMetadata(CompactMetadata compactMetadata) { this.compactMetadata = compactMetadata; } + /** + * Represents MCP server information. + */ public static class McpServer { + /** + * Server name. + */ private String name; + /** + * Server status. + */ private String status; - // Getters and setters + /** + * Gets the server name. + * + * @return The server name + */ public String getName() { return name; } + /** + * Sets the server name. + * + * @param name The server name + */ public void setName(String name) { this.name = name; } + /** + * Gets the server status. + * + * @return The server status + */ public String getStatus() { return status; } + /** + * Sets the server status. + * + * @param status The server status + */ public void setStatus(String status) { this.status = status; } } + /** + * Represents compact metadata. + */ public static class CompactMetadata { + /** + * Trigger information. + */ private String trigger; + /** + * Pre-tokens information. + */ @JSONField(name = "pre_tokens") private Integer preTokens; - // Getters and setters + /** + * Gets the trigger information. + * + * @return The trigger information + */ public String getTrigger() { return trigger; } + /** + * Sets the trigger information. + * + * @param trigger The trigger information + */ public void setTrigger(String trigger) { this.trigger = trigger; } + /** + * Gets the pre-tokens information. + * + * @return The pre-tokens information + */ public Integer getPreTokens() { return preTokens; } + /** + * Sets the pre-tokens information. + * + * @param preTokens The pre-tokens information + */ public void setPreTokens(Integer preTokens) { this.preTokens = preTokens; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java index e896b08c4..bdd69f01d 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java @@ -5,84 +5,187 @@ import java.util.Map; import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Represents a user message in the SDK protocol. + */ @JSONType(typeKey = "type", typeName = "user") public class SDKUserMessage extends MessageBase { + /** + * The UUID of the message. + */ private String uuid; + /** + * The session ID. + */ @JSONField(name = "session_id") private String sessionId; + /** + * The API user message. + */ private final APIUserMessage message = new APIUserMessage(); + /** + * The parent tool use ID. + */ @JSONField(name = "parent_tool_use_id") private String parentToolUseId; + /** + * Additional options. + */ private Map options; + /** + * Creates a new SDKUserMessage instance and sets the type to "user". + */ public SDKUserMessage() { super(); this.setType("user"); } + /** + * Gets the UUID of the message. + * + * @return The UUID of the message + */ public String getUuid() { return uuid; } + /** + * Sets the UUID of the message. + * + * @param uuid The UUID of the message + */ public void setUuid(String uuid) { this.uuid = uuid; } + /** + * Gets the session ID. + * + * @return The session ID + */ public String getSessionId() { return sessionId; } + /** + * Sets the session ID. + * + * @param sessionId The session ID + * @return This instance for method chaining + */ public SDKUserMessage setSessionId(String sessionId) { this.sessionId = sessionId; return this; } + /** + * Sets the content of the message. + * + * @param content The content of the message + * @return This instance for method chaining + */ public SDKUserMessage setContent(String content) { message.setContent(content); return this; } + /** + * Gets the content of the message. + * + * @return The content of the message + */ public String getContent() { return message.getContent(); } + /** + * Gets the parent tool use ID. + * + * @return The parent tool use ID + */ public String getParentToolUseId() { return parentToolUseId; } + /** + * Sets the parent tool use ID. + * + * @param parentToolUseId The parent tool use ID + * @return This instance for method chaining + */ public SDKUserMessage setParentToolUseId(String parentToolUseId) { this.parentToolUseId = parentToolUseId; return this; } + /** + * Gets the additional options. + * + * @return The additional options + */ public Map getOptions() { return options; } + /** + * Sets the additional options. + * + * @param options The additional options + * @return This instance for method chaining + */ public SDKUserMessage setOptions(Map options) { this.options = options; return this; } + /** + * Represents the API user message. + */ public static class APIUserMessage { + /** + * User role. + */ private String role = "user"; + /** + * Message content. + */ private String content; - // Getters and Setters + /** + * Gets the user role. + * + * @return The user role + */ public String getRole() { return role; } + /** + * Sets the user role. + * + * @param role The user role + */ public void setRole(String role) { this.role = role; } + /** + * Gets the message content. + * + * @return The message content + */ public String getContent() { return content; } + /** + * Sets the message content. + * + * @param content The message content + */ public void setContent(String content) { this.content = content; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java index 5a0b3776c..b64952228 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java @@ -6,71 +6,164 @@ import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.qwen.code.cli.protocol.data.Usage; import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock; +/** + * Represents an API assistant message. + */ public class APIAssistantMessage { + /** + * Message ID. + */ private String id; + /** + * Message type. + */ private String type = "message"; + /** + * Message role. + */ private String role = "assistant"; + /** + * Message model. + */ private String model; - private List content; + /** + * Message content. + */ + private List> content; + /** + * Stop reason. + */ @JSONField(name = "stop_reason") private String stopReason; + /** + * Usage information. + */ private Usage usage; - // Getters and setters + /** + * Gets the message ID. + * + * @return The message ID + */ public String getId() { return id; } + /** + * Sets the message ID. + * + * @param id The message ID + */ public void setId(String id) { this.id = id; } + /** + * Gets the message type. + * + * @return The message type + */ public String getType() { return type; } + /** + * Sets the message type. + * + * @param type The message type + */ public void setType(String type) { this.type = type; } + /** + * Gets the message role. + * + * @return The message role + */ public String getRole() { return role; } + /** + * Sets the message role. + * + * @param role The message role + */ public void setRole(String role) { this.role = role; } + /** + * Gets the message model. + * + * @return The message model + */ public String getModel() { return model; } + /** + * Sets the message model. + * + * @param model The message model + */ public void setModel(String model) { this.model = model; } + /** + * Gets the stop reason. + * + * @return The stop reason + */ public String getStopReason() { return stopReason; } + /** + * Sets the stop reason. + * + * @param stopReason The stop reason + */ public void setStopReason(String stopReason) { this.stopReason = stopReason; } + /** + * Gets the usage information. + * + * @return The usage information + */ public Usage getUsage() { return usage; } + /** + * Sets the usage information. + * + * @param usage The usage information + */ public void setUsage(Usage usage) { this.usage = usage; } - public List getContent() { + /** + * Gets the message content. + * + * @return The message content + */ + public List> getContent() { return content; } - public void setContent(List content) { + /** + * Sets the message content. + * + * @param content The message content + */ + public void setContent(List> content) { this.content = content; } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java index b0a3012c4..efb6071cf 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java @@ -4,50 +4,113 @@ import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.message.MessageBase; +/** + * Represents an SDK assistant message. + */ @JSONType(typeKey = "type", typeName = "assistant") public class SDKAssistantMessage extends MessageBase { + /** + * The UUID of the message. + */ private String uuid; + /** + * The session ID. + */ @JSONField(name = "session_id") private String sessionId; + /** + * The API assistant message. + */ private APIAssistantMessage message; + /** + * The parent tool use ID. + */ @JSONField(name = "parent_tool_use_id") private String parentToolUseId; + /** + * Creates a new SDKAssistantMessage instance and sets the type to "assistant". + */ public SDKAssistantMessage() { super(); this.type = "assistant"; } + @Override + public String getMessageId() { + return this.getUuid(); + } + + /** + * Gets the UUID of the message. + * + * @return The UUID of the message + */ public String getUuid() { return uuid; } + /** + * Sets the UUID of the message. + * + * @param uuid The UUID of the message + */ public void setUuid(String uuid) { this.uuid = uuid; } + /** + * Gets the session ID. + * + * @return The session ID + */ public String getSessionId() { return sessionId; } + /** + * Sets the session ID. + * + * @param sessionId The session ID + */ public void setSessionId(String sessionId) { this.sessionId = sessionId; } + /** + * Gets the API assistant message. + * + * @return The API assistant message + */ public APIAssistantMessage getMessage() { return message; } + /** + * Sets the API assistant message. + * + * @param message The API assistant message + */ public void setMessage(APIAssistantMessage message) { this.message = message; } + /** + * Gets the parent tool use ID. + * + * @return The parent tool use ID + */ public String getParentToolUseId() { return parentToolUseId; } + /** + * Sets the parent tool use ID. + * + * @param parentToolUseId The parent tool use ID + */ public void setParentToolUseId(String parentToolUseId) { this.parentToolUseId = parentToolUseId; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java index a9ac24d05..2c7cc0934 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java @@ -5,50 +5,108 @@ import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.message.MessageBase; import com.alibaba.qwen.code.cli.protocol.message.assistant.event.StreamEvent; +/** + * Represents a partial assistant message during streaming. + */ @JSONType(typeKey = "type", typeName = "stream_event") public class SDKPartialAssistantMessage extends MessageBase { + /** + * The UUID of the message. + */ private String uuid; + /** + * The session ID. + */ @JSONField(name = "session_id") private String sessionId; + /** + * The stream event. + */ private StreamEvent event; + /** + * The parent tool use ID. + */ @JSONField(name = "parent_tool_use_id") private String parentToolUseId; + /** + * Creates a new SDKPartialAssistantMessage instance and sets the type to "stream_event". + */ public SDKPartialAssistantMessage() { super(); this.type = "stream_event"; } + /** + * Gets the UUID of the message. + * + * @return The UUID of the message + */ public String getUuid() { return uuid; } + /** + * Sets the UUID of the message. + * + * @param uuid The UUID of the message + */ public void setUuid(String uuid) { this.uuid = uuid; } + /** + * Gets the session ID. + * + * @return The session ID + */ public String getSessionId() { return sessionId; } + /** + * Sets the session ID. + * + * @param sessionId The session ID + */ public void setSessionId(String sessionId) { this.sessionId = sessionId; } + /** + * Gets the stream event. + * + * @return The stream event + */ public StreamEvent getEvent() { return event; } + /** + * Sets the stream event. + * + * @param event The stream event + */ public void setEvent(StreamEvent event) { this.event = event; } + /** + * Gets the parent tool use ID. + * + * @return The parent tool use ID + */ public String getParentToolUseId() { return parentToolUseId; } + /** + * Sets the parent tool use ID. + * + * @param parentToolUseId The parent tool use ID + */ public void setParentToolUseId(String parentToolUseId) { this.parentToolUseId = parentToolUseId; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java index 5e8b9a2b5..e78cf3576 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java @@ -2,26 +2,54 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.block; import com.alibaba.fastjson2.annotation.JSONField; +/** + * Represents an annotation for a content block. + */ public class Annotation { + /** + * The annotation type. + */ @JSONField(name = "type") private String type; + /** + * The annotation value. + */ @JSONField(name = "value") private String value; - // Getters and setters + /** + * Gets the annotation type. + * + * @return The annotation type + */ public String getType() { return type; } + /** + * Sets the annotation type. + * + * @param type The annotation type + */ public void setType(String type) { this.type = type; } + /** + * Gets the annotation value. + * + * @return The annotation value + */ public String getValue() { return value; } + /** + * Sets the annotation value. + * + * @param value The annotation value + */ public void setValue(String value) { this.value = value; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java index d40200c6e..c8c7284b0 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java @@ -6,27 +6,72 @@ import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +/** + * Abstract base class for content blocks in assistant messages. + * + * @param The type of content + */ @JSONType(typeKey = "type", typeName = "ContentBlock", seeAlso = { TextBlock.class, ToolResultBlock.class, ThinkingBlock.class, ToolUseBlock.class }) -public abstract class ContentBlock implements AssistantContent { +public abstract class ContentBlock implements AssistantContent { + /** + * The type of the content block. + */ protected String type; + /** + * List of annotations. + */ protected List annotations; + /** + * The message ID. + */ + protected String messageId; + @Override public String getType() { return type; } + /** + * Sets the type of the content block. + * + * @param type The type of the content block + */ public void setType(String type) { this.type = type; } + /** + * Gets the list of annotations. + * + * @return The list of annotations + */ public List getAnnotations() { return annotations; } + /** + * Sets the list of annotations. + * + * @param annotations The list of annotations + */ public void setAnnotations(List annotations) { this.annotations = annotations; } + @Override + public String getMessageId() { + return messageId; + } + + /** + * Sets the message ID. + * + * @param messageId The message ID + */ + public void setMessageId(String messageId) { + this.messageId = messageId; + } + public String toString() { return JSON.toJSONString(this); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java index 7a8cf7d43..9980a74b8 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java @@ -1,21 +1,38 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.block; import com.alibaba.fastjson2.annotation.JSONType; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +/** + * Represents a text content block. + */ @JSONType(typeKey = "type", typeName = "text") -public class TextBlock extends ContentBlock { +public class TextBlock extends ContentBlock implements TextAssistantContent { + /** + * The text content. + */ private String text; + /** + * Gets the text content. + * + * @return The text content + */ public String getText() { return text; } + /** + * Sets the text content. + * + * @param text The text content + */ public void setText(String text) { this.text = text; } @Override - public Object getContent() { + public String getContentOfAssistant() { return text; } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java index 4a133840f..9b33730bc 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java @@ -1,30 +1,60 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.block; import com.alibaba.fastjson2.annotation.JSONType; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +/** + * Represents a thinking content block. + */ @JSONType(typeKey = "type", typeName = "thinking") -public class ThinkingBlock extends ContentBlock{ +public class ThinkingBlock extends ContentBlock implements ThingkingAssistantContent { + /** + * The thinking content. + */ private String thinking; + /** + * The signature. + */ private String signature; + /** + * Gets the thinking content. + * + * @return The thinking content + */ public String getThinking() { return thinking; } + /** + * Sets the thinking content. + * + * @param thinking The thinking content + */ public void setThinking(String thinking) { this.thinking = thinking; } + /** + * Gets the signature. + * + * @return The signature + */ public String getSignature() { return signature; } + /** + * Sets the signature. + * + * @param signature The signature + */ public void setSignature(String signature) { this.signature = signature; } @Override - public Object getContent() { + public String getContentOfAssistant() { return thinking; } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java index 3d7acfea2..43da74b0c 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java @@ -2,39 +2,87 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.block; import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +/** + * Represents a tool result content block. + */ @JSONType(typeKey = "type", typeName = "tool_result") -public class ToolResultBlock extends ContentBlock { +public class ToolResultBlock extends ContentBlock implements ToolResultAssistantContent { + /** + * The tool use ID. + */ @JSONField(name = "tool_use_id") private String toolUseId; + /** + * The result content. + */ @JSONField(name = "content") - private Object content; // Can be String or List + private String content; + /** + * Whether the result is an error. + */ @JSONField(name = "is_error") private Boolean isError; + /** + * Gets the tool use ID. + * + * @return The tool use ID + */ public String getToolUseId() { return toolUseId; } + /** + * Sets the tool use ID. + * + * @param toolUseId The tool use ID + */ public void setToolUseId(String toolUseId) { this.toolUseId = toolUseId; } - public Object getContent() { + /** + * Gets the result content. + * + * @return The result content + */ + public String getContent() { return content; } - public void setContent(Object content) { + /** + * Sets the result content. + * + * @param content The result content + */ + public void setContent(String content) { this.content = content; } + /** + * Gets whether the result is an error. + * + * @return Whether the result is an error + */ public Boolean getIsError() { return isError; } + /** + * Sets whether the result is an error. + * + * @param isError Whether the result is an error + */ public void setIsError(Boolean isError) { this.isError = isError; } + + @Override + public String getContentOfAssistant() { + return content; + } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java index ef5de8b02..da3624a67 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java @@ -1,54 +1,113 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.block; +import java.util.Collections; import java.util.List; import java.util.Map; import com.alibaba.fastjson2.annotation.JSONType; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +/** + * Represents a tool use content block. + */ @JSONType(typeKey = "type", typeName = "tool_use") -public class ToolUseBlock extends ContentBlock { +public class ToolUseBlock extends ContentBlock> implements ToolUseAssistantContent { + /** + * The tool use ID. + */ private String id; + /** + * The tool name. + */ private String name; + /** + * The tool input. + */ private Map input; + /** + * List of annotations. + */ private List annotations; - // 构造函数 + /** + * Creates a new ToolUseBlock instance. + */ public ToolUseBlock() {} + /** + * Gets the tool use ID. + * + * @return The tool use ID + */ public String getId() { return id; } + /** + * Sets the tool use ID. + * + * @param id The tool use ID + */ public void setId(String id) { this.id = id; } + /** + * Gets the tool name. + * + * @return The tool name + */ public String getName() { return name; } + /** + * Sets the tool name. + * + * @param name The tool name + */ public void setName(String name) { this.name = name; } + /** + * Gets the tool input. + * + * @return The tool input + */ public Map getInput() { return input; } + /** + * Sets the tool input. + * + * @param input The tool input + */ public void setInput(Map input) { this.input = input; } + /** + * Gets the list of annotations. + * + * @return The list of annotations + */ public List getAnnotations() { return annotations; } + /** + * Sets the list of annotations. + * + * @param annotations The list of annotations + */ public void setAnnotations(List annotations) { this.annotations = annotations; } @Override - public Object getContent() { - return input; + public Map getContentOfAssistant() { + return Collections.emptyMap(); } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java index 78b7961cc..6486404f8 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java @@ -1,96 +1,221 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.event; +import java.util.Map; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +/** + * Represents a content block delta event during streaming. + */ @JSONType(typeKey = "type", typeName = "content_block_delta") public class ContentBlockDeltaEvent extends StreamEvent { + /** + * The index of the content block. + */ private int index; - private ContentBlockDelta delta; + /** + * The content block delta. + */ + private ContentBlockDelta delta; + /** + * Gets the index of the content block. + * + * @return The index of the content block + */ public int getIndex() { return index; } + /** + * Sets the index of the content block. + * + * @param index The index of the content block + */ public void setIndex(int index) { this.index = index; } - public ContentBlockDelta getDelta() { + /** + * Gets the content block delta. + * + * @return The content block delta + */ + public ContentBlockDelta getDelta() { return delta; } - public void setDelta(ContentBlockDelta delta) { + /** + * Sets the content block delta. + * + * @param delta The content block delta + */ + public void setDelta(ContentBlockDelta delta) { this.delta = delta; } + /** + * Abstract base class for content block deltas. + * + * @param The type of content + */ @JSONType(typeKey = "type", typeName = "ContentBlockDelta", seeAlso = {ContentBlockDeltaText.class, ContentBlockDeltaThinking.class, ContentBlockDeltaInputJson.class}) - public abstract static class ContentBlockDelta implements AssistantContent { - private String type; + public abstract static class ContentBlockDelta implements AssistantContent { + /** + * The type of the content block delta. + */ + protected String type; + /** + * The message ID. + */ + protected String messageId; + @Override public String getType() { return type; } + /** + * Sets the type of the content block delta. + * + * @param type The type of the content block delta + */ public void setType(String type) { this.type = type; } + + @Override + public String getMessageId() { + return messageId; + } + + /** + * Sets the message ID. + * + * @param messageId The message ID + */ + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public String toString() { + return JSON.toJSONString(this); + } } + /** + * Represents a text delta. + */ @JSONType(typeKey = "type", typeName = "text_delta") - public static class ContentBlockDeltaText extends ContentBlockDelta { + public static class ContentBlockDeltaText extends ContentBlockDelta implements TextAssistantContent { + /** + * The text content. + */ private String text; + /** + * Gets the text content. + * + * @return The text content + */ public String getText() { return text; } + /** + * Sets the text content. + * + * @param text The text content + */ public void setText(String text) { this.text = text; } @Override - public Object getContent() { + public String getContentOfAssistant() { return text; } } + /** + * Represents a thinking delta. + */ @JSONType(typeKey = "type", typeName = "thinking_delta") - public static class ContentBlockDeltaThinking extends ContentBlockDelta { + public static class ContentBlockDeltaThinking extends ContentBlockDelta implements ThingkingAssistantContent { + /** + * The thinking content. + */ private String thinking; + /** + * Gets the thinking content. + * + * @return The thinking content + */ public String getThinking() { return thinking; } + /** + * Sets the thinking content. + * + * @param thinking The thinking content + */ public void setThinking(String thinking) { this.thinking = thinking; } @Override - public Object getContent() { + public String getContentOfAssistant() { return thinking; } } + /** + * Represents an input JSON delta. + */ @JSONType(typeKey = "type", typeName = "input_json_delta") - public static class ContentBlockDeltaInputJson extends ContentBlockDelta { + public static class ContentBlockDeltaInputJson extends ContentBlockDelta> implements ToolUseAssistantContent { + /** + * The partial JSON content. + */ @JSONField(name = "partial_json") private String partialJson; + /** + * Gets the partial JSON content. + * + * @return The partial JSON content + */ public String getPartialJson() { return partialJson; } + /** + * Sets the partial JSON content. + * + * @param partialJson The partial JSON content + */ public void setPartialJson(String partialJson) { this.partialJson = partialJson; } @Override - public Object getContent() { - return partialJson; + public Map getContentOfAssistant() { + return getInput(); + } + + @Override + public Map getInput() { + return JSON.parseObject(partialJson, new TypeReference>() {}); } } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java index 884558512..eaf132934 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java @@ -4,10 +4,19 @@ import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock; +/** + * Represents a content block start event during message streaming. + */ @JSONType(typeKey = "type", typeName = "content_block_start") public class ContentBlockStartEvent extends StreamEvent{ + /** + * The index of the content block. + */ private int index; + /** + * The content block that is starting. + */ @JSONField(name = "content_block") private ContentBlock contentBlock; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java index 0e950f817..9b4529b83 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java @@ -2,14 +2,30 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.event; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Represents a content block stop event during message streaming. + */ @JSONType(typeKey = "type", typeName = "content_block_stop") public class ContentBlockStopEvent extends StreamEvent{ + /** + * The index of the content block. + */ Long index; + /** + * Gets the index of the content block. + * + * @return The index of the content block + */ public Long getIndex() { return index; } + /** + * Sets the index of the content block. + * + * @param index The index of the content block + */ public void setIndex(Long index) { this.index = index; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java index 88be40545..cdd89ba4a 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java @@ -2,45 +2,102 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.event; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Represents a message start event during message streaming. + */ @JSONType(typeName = "message_start") public class MessageStartStreamEvent extends StreamEvent{ + /** + * The message that is starting. + */ private Message message; + /** + * Represents the message information. + */ public static class Message { + /** + * Message ID. + */ private String id; + /** + * Message role. + */ private String role; + /** + * Message model. + */ private String model; - // Getters and setters + /** + * Gets the message ID. + * + * @return The message ID + */ public String getId() { return id; } + /** + * Sets the message ID. + * + * @param id The message ID + */ public void setId(String id) { this.id = id; } + /** + * Gets the message role. + * + * @return The message role + */ public String getRole() { return role; } + /** + * Sets the message role. + * + * @param role The message role + */ public void setRole(String role) { this.role = role; } + /** + * Gets the message model. + * + * @return The message model + */ public String getModel() { return model; } + /** + * Sets the message model. + * + * @param model The message model + */ public void setModel(String model) { this.model = model; } } + /** + * Gets the message that is starting. + * + * @return The message that is starting + */ public Message getMessage() { return message; } + /** + * Sets the message that is starting. + * + * @param message The message that is starting + */ public void setMessage(Message message) { this.message = message; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java index 3ea04bc50..602ae4dfd 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.event; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Represents a message stop event during message streaming. + */ @JSONType(typeName = "message_stop") public class MessageStopStreamEvent extends StreamEvent{ } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java index d288402fa..1a4627dd5 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java @@ -2,16 +2,32 @@ package com.alibaba.qwen.code.cli.protocol.message.assistant.event; import com.alibaba.fastjson2.annotation.JSONType; +/** + * Base class for stream events during message streaming. + */ @JSONType(typeKey = "type", typeName = "StreamEvent", seeAlso = {MessageStartStreamEvent.class, MessageStopStreamEvent.class, ContentBlockStartEvent.class, ContentBlockStopEvent.class, ContentBlockDeltaEvent.class}) public class StreamEvent { + /** + * The type of the stream event. + */ protected String type; + /** + * Gets the type of the stream event. + * + * @return The type of the stream event + */ public String getType() { return type; } + /** + * Sets the type of the stream event. + * + * @param type The type of the stream event + */ public void setType(String type) { this.type = type; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java index 3d217289c..6ddc66e41 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java @@ -3,24 +3,54 @@ package com.alibaba.qwen.code.cli.protocol.message.control; import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.qwen.code.cli.protocol.data.InitializeConfig; +/** + * Represents a control initialize request to the CLI. + */ public class CLIControlInitializeRequest { + /** + * The subtype of the request. + */ String subtype = "initialize"; + /** + * The initialization configuration. + */ @JSONField(unwrapped = true) InitializeConfig initializeConfig = new InitializeConfig(); + /** + * Gets the subtype of the request. + * + * @return The subtype of the request + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the request. + * + * @param subtype The subtype of the request + */ public void setSubtype(String subtype) { this.subtype = subtype; } + /** + * Gets the initialization configuration. + * + * @return The initialization configuration + */ public InitializeConfig getInitializeConfig() { return initializeConfig; } + /** + * Sets the initialization configuration. + * + * @param initializeConfig The initialization configuration + * @return This instance for method chaining + */ public CLIControlInitializeRequest setInitializeConfig(InitializeConfig initializeConfig) { this.initializeConfig = initializeConfig; return this; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java index 284781a76..69da95deb 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java @@ -2,22 +2,51 @@ package com.alibaba.qwen.code.cli.protocol.message.control; import com.alibaba.qwen.code.cli.protocol.data.Capabilities; +/** + * Represents a control initialize response from the CLI. + */ public class CLIControlInitializeResponse { + /** + * The subtype of the response. + */ String subtype = "initialize"; + /** + * The capabilities' information. + */ Capabilities capabilities; + /** + * Gets the subtype of the response. + * + * @return The subtype of the response + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the response. + * + * @param subtype The subtype of the response + */ public void setSubtype(String subtype) { this.subtype = subtype; } + /** + * Gets the capabilities information. + * + * @return The capabilities information + */ public Capabilities getCapabilities() { return capabilities; } + /** + * Sets the capabilities information. + * + * @param capabilities The capabilities information + */ public void setCapabilities(Capabilities capabilities) { this.capabilities = capabilities; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java index f4a052697..2b1ec9fc5 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java @@ -1,12 +1,28 @@ package com.alibaba.qwen.code.cli.protocol.message.control; +/** + * Represents a control interrupt request to the CLI. + */ public class CLIControlInterruptRequest { + /** + * The subtype of the request ("interrupt"). + */ String subtype = "interrupt"; + /** + * Gets the subtype of the request. + * + * @return The subtype of the request + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the request. + * + * @param subtype The subtype of the request + */ public void setSubtype(String subtype) { this.subtype = subtype; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java index ac3e43e79..e03d86bad 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java @@ -5,106 +5,242 @@ import java.util.Map; import com.alibaba.fastjson2.annotation.JSONField; +/** + * Represents a control permission request to the CLI. + */ public class CLIControlPermissionRequest { + /** + * The subtype of the request. + */ private String subtype; + /** + * The name of the tool requesting permission. + */ @JSONField(name = "tool_name") private String toolName; + /** + * The ID of the tool use. + */ @JSONField(name = "tool_use_id") private String toolUseId; + /** + * The input for the tool. + */ private Map input; + /** + * List of permission suggestions. + */ @JSONField(name = "permission_suggestions") private List permissionSuggestions; + /** + * The blocked path. + */ @JSONField(name = "blocked_path") private String blockedPath; + /** + * Gets the subtype of the request. + * + * @return The subtype of the request + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the request. + * + * @param subtype The subtype of the request + */ public void setSubtype(String subtype) { this.subtype = subtype; } + /** + * Gets the name of the tool requesting permission. + * + * @return The name of the tool requesting permission + */ public String getToolName() { return toolName; } + /** + * Sets the name of the tool requesting permission. + * + * @param toolName The name of the tool requesting permission + */ public void setToolName(String toolName) { this.toolName = toolName; } + /** + * Gets the ID of the tool use. + * + * @return The ID of the tool use + */ public String getToolUseId() { return toolUseId; } + /** + * Sets the ID of the tool use. + * + * @param toolUseId The ID of the tool use + */ public void setToolUseId(String toolUseId) { this.toolUseId = toolUseId; } + /** + * Gets the input for the tool. + * + * @return The input for the tool + */ public Map getInput() { return input; } + /** + * Sets the input for the tool. + * + * @param input The input for the tool + */ public void setInput(Map input) { this.input = input; } + /** + * Gets the list of permission suggestions. + * + * @return The list of permission suggestions + */ public List getPermissionSuggestions() { return permissionSuggestions; } + /** + * Sets the list of permission suggestions. + * + * @param permissionSuggestions The list of permission suggestions + */ public void setPermissionSuggestions( List permissionSuggestions) { this.permissionSuggestions = permissionSuggestions; } + /** + * Gets the blocked path. + * + * @return The blocked path + */ public String getBlockedPath() { return blockedPath; } + /** + * Sets the blocked path. + * + * @param blockedPath The blocked path + */ public void setBlockedPath(String blockedPath) { this.blockedPath = blockedPath; } + /** + * Represents a permission suggestion. + */ public static class PermissionSuggestion { + /** + * The type of suggestion (allow, deny, modify). + */ private String type; // 'allow' | 'deny' | 'modify' + /** + * The label for the suggestion. + */ private String label; + /** + * The description of the suggestion. + */ private String description; + /** + * The modified input. + */ private Object modifiedInput; + /** + * Gets the type of suggestion. + * + * @return The type of suggestion + */ public String getType() { return type; } + /** + * Sets the type of suggestion. + * + * @param type The type of suggestion + */ public void setType(String type) { this.type = type; } + /** + * Gets the label for the suggestion. + * + * @return The label for the suggestion + */ public String getLabel() { return label; } + /** + * Sets the label for the suggestion. + * + * @param label The label for the suggestion + */ public void setLabel(String label) { this.label = label; } + /** + * Gets the description of the suggestion. + * + * @return The description of the suggestion + */ public String getDescription() { return description; } + /** + * Sets the description of the suggestion. + * + * @param description The description of the suggestion + */ public void setDescription(String description) { this.description = description; } + /** + * Gets the modified input. + * + * @return The modified input + */ public Object getModifiedInput() { return modifiedInput; } + /** + * Sets the modified input. + * + * @param modifiedInput The modified input + */ public void setModifiedInput(Object modifiedInput) { this.modifiedInput = modifiedInput; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java index 66c199632..2a1d22588 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java @@ -3,24 +3,54 @@ 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; +/** + * Represents a control permission response from the CLI. + */ public class CLIControlPermissionResponse { + /** + * The subtype of the response ("can_use_tool"). + */ private String subtype = "can_use_tool"; + /** + * The behavior for the permission request. + */ @JSONField(unwrapped = true) Behavior behavior; + /** + * Gets the subtype of the response. + * + * @return The subtype of the response + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the response. + * + * @param subtype The subtype of the response + */ public void setSubtype(String subtype) { this.subtype = subtype; } + /** + * Gets the behavior for the permission request. + * + * @return The behavior for the permission request + */ public Behavior getBehavior() { return behavior; } + /** + * Sets the behavior for the permission request. + * + * @param behavior The behavior for the permission request + * @return This instance for method chaining + */ public CLIControlPermissionResponse setBehavior(Behavior behavior) { this.behavior = behavior; return this; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java index e6ea7b956..e12319cad 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java @@ -6,37 +6,80 @@ import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.message.MessageBase; +/** + * Represents a control request to the CLI. + * + * @param The type of the request object + */ @JSONType(typeKey = "type", typeName = "control_request") public class CLIControlRequest extends MessageBase { + /** + * The ID of the request. + */ @JSONField(name = "request_id") private String requestId = UUID.randomUUID().toString(); + /** + * The actual request object. + */ private R request; + /** + * Creates a new CLIControlRequest instance and sets the type to "control_request". + */ public CLIControlRequest() { super(); type = "control_request"; } + /** + * Creates a new control request with the specified request object. + * + * @param request The request object + * @param The type of the request object + * @return A new control request instance + */ public static CLIControlRequest create(T request) { CLIControlRequest controlRequest = new CLIControlRequest<>(); controlRequest.setRequest(request); return controlRequest; } + /** + * Gets the ID of the request. + * + * @return The ID of the request + */ public String getRequestId() { return requestId; } + /** + * Sets the ID of the request. + * + * @param requestId The ID of the request + * @return This instance for method chaining + */ public CLIControlRequest setRequestId(String requestId) { this.requestId = requestId; return this; } + /** + * Gets the actual request object. + * + * @return The actual request object + */ public R getRequest() { return request; } + /** + * Sets the actual request object. + * + * @param request The actual request object + * @return This instance for method chaining + */ public CLIControlRequest setRequest(R request) { this.request = request; return this; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java index bce0c03cc..f71c2156c 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java @@ -4,57 +4,130 @@ import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.message.MessageBase; +/** + * Represents a control response from the CLI. + * + * @param The type of the response object + */ @JSONType(typeKey = "type", typeName = "control_response") public class CLIControlResponse extends MessageBase { + /** + * The response object. + */ private Response response; + /** + * Creates a new CLIControlResponse instance and sets the type to "control_response". + */ public CLIControlResponse() { super(); this.type = "control_response"; } + /** + * Gets the response object. + * + * @return The response object + */ public Response getResponse() { return response; } + /** + * Sets the response object. + * + * @param response The response object + */ public void setResponse(Response response) { this.response = response; } + /** + * Creates a new response object. + * + * @return A new response object + */ public Response createResponse() { Response response = new Response<>(); this.setResponse(response); return response; } + /** + * Represents the response information. + * + * @param The type of the response object + */ public static class Response { + /** + * The ID of the request. + */ @JSONField(name = "request_id") private String requestId; + /** + * The subtype of the response. + */ private String subtype = "success"; + /** + * The actual response. + */ R response; + /** + * Gets the ID of the request. + * + * @return The ID of the request + */ public String getRequestId() { return requestId; } + /** + * Sets the ID of the request. + * + * @param requestId The ID of the request + * @return This instance for method chaining + */ public Response setRequestId(String requestId) { this.requestId = requestId; return this; } + /** + * Gets the subtype of the response. + * + * @return The subtype of the response + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the response. + * + * @param subtype The subtype of the response + * @return This instance for method chaining + */ public Response setSubtype(String subtype) { this.subtype = subtype; return this; } + /** + * Gets the actual response. + * + * @return The actual response + */ public R getResponse() { return response; } + /** + * Sets the actual response. + * + * @param response The actual response + * @return This instance for method chaining + */ public Response setResponse(R response) { this.response = response; return this; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java index d93a6fb6d..8b1e5d8dc 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java @@ -1,21 +1,50 @@ package com.alibaba.qwen.code.cli.protocol.message.control; +/** + * Represents a control request to set the model in the CLI. + */ public class CLIControlSetModelRequest { + /** + * The subtype of the request ("set_model"). + */ String subtype = "set_model"; + /** + * The model to set. + */ String model; + /** + * Gets the subtype of the request. + * + * @return The subtype of the request + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the request. + * + * @param subtype The subtype of the request + */ public void setSubtype(String subtype) { this.subtype = subtype; } + /** + * Gets the model to set. + * + * @return The model to set + */ public String getModel() { return model; } + /** + * Sets the model to set. + * + * @param model The model to set + */ public void setModel(String model) { this.model = model; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java index 71d6b0e38..d5a0c2c87 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java @@ -1,21 +1,50 @@ package com.alibaba.qwen.code.cli.protocol.message.control; +/** + * Represents a control response for setting the model in the CLI. + */ public class CLIControlSetModelResponse { + /** + * The subtype of the response ("set_model"). + */ String subtype = "set_model"; + /** + * The model that was set. + */ String model; + /** + * Gets the subtype of the response. + * + * @return The subtype of the response + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the response. + * + * @param subtype The subtype of the response + */ public void setSubtype(String subtype) { this.subtype = subtype; } + /** + * Gets the model that was set. + * + * @return The model that was set + */ public String getModel() { return model; } + /** + * Sets the model that was set. + * + * @param model The model that was set + */ public void setModel(String model) { this.model = model; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java index ea1ad9698..587fafdc5 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java @@ -1,22 +1,51 @@ package com.alibaba.qwen.code.cli.protocol.message.control; +/** + * Represents a control request to set the permission mode in the CLI. + */ public class CLIControlSetPermissionModeRequest { + /** + * The subtype of the request ("set_permission_mode"). + */ String subtype = "set_permission_mode"; + /** + * The permission mode to set. + */ String mode; + /** + * Gets the subtype of the request. + * + * @return The subtype of the request + */ public String getSubtype() { return subtype; } + /** + * Sets the subtype of the request. + * + * @param subtype The subtype of the request + */ public void setSubtype(String subtype) { this.subtype = subtype; } + /** + * Gets the permission mode to set. + * + * @return The permission mode to set + */ public String getMode() { return mode; } + /** + * Sets the permission mode to set. + * + * @param mode The permission mode to set + */ public void setMode(String mode) { this.mode = mode; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java index c3e605476..2706bcb1a 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java @@ -31,6 +31,7 @@ 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.transport.TransportOptions; import com.alibaba.qwen.code.cli.utils.MyConcurrentUtils; import com.alibaba.qwen.code.cli.utils.Timeout; @@ -38,6 +39,9 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Manages a session with the Qwen Code CLI, handling communication, sending prompts, and processing responses. + */ public class Session { private static final Logger log = LoggerFactory.getLogger(Session.class); private final Transport transport; @@ -45,6 +49,24 @@ public class Session { private SDKSystemMessage lastSdkSystemMessage; private final Timeout defaultEventTimeout = Timeout.TIMEOUT_60_SECONDS; + /** + * Checks if the session is configured for streaming. + * + * @return true if streaming is enabled, false otherwise + */ + public boolean isStreaming() { + return Optional.ofNullable(transport) + .map(Transport::getTransportOptions) + .map(TransportOptions::getIncludePartialMessages) + .orElse(false); + } + + /** + * Constructs a new session with the specified transport. + * + * @param transport The transport layer to use for communication + * @throws SessionControlException if the transport is not available + */ public Session(Transport transport) throws SessionControlException { if (transport == null || !transport.isAvailable()) { throw new SessionControlException("Transport is not available"); @@ -53,6 +75,11 @@ public class Session { start(); } + /** + * Starts the session by initializing communication with the CLI. + * + * @throws SessionControlException if initialization fails + */ public void start() throws SessionControlException { try { if (!transport.isAvailable()) { @@ -67,6 +94,11 @@ public class Session { } } + /** + * Closes the session and releases resources. + * + * @throws SessionControlException if closing fails + */ public void close() throws SessionControlException { try { transport.close(); @@ -75,11 +107,24 @@ public class Session { } } + /** + * Interrupts the current operation in the CLI. + * + * @return An optional boolean indicating success of the interrupt operation + * @throws SessionControlException if the operation fails + */ public Optional interrupt() throws SessionControlException { checkAvailable(); return processControlRequest(new CLIControlRequest().setRequest(new CLIControlInterruptRequest()).toString()); } + /** + * Sets the model to be used in the session. + * + * @param modelName The name of the model to use + * @return An optional boolean indicating success of the operation + * @throws SessionControlException if the operation fails + */ public Optional setModel(String modelName) throws SessionControlException { checkAvailable(); CLIControlSetModelRequest cliControlSetModelRequest = new CLIControlSetModelRequest(); @@ -87,6 +132,13 @@ public class Session { return processControlRequest(new CLIControlRequest().setRequest(cliControlSetModelRequest).toString()); } + /** + * Sets the permission mode for the session. + * + * @param permissionMode The permission mode to use + * @return An optional boolean indicating success of the operation + * @throws SessionControlException if the operation fails + */ public Optional setPermissionMode(PermissionMode permissionMode) throws SessionControlException { checkAvailable(); CLIControlSetPermissionModeRequest cliControlSetPermissionModeRequest = new CLIControlSetPermissionModeRequest(); @@ -110,10 +162,21 @@ public class Session { } } + /** + * Continues the current session. + * + * @throws SessionControlException if the operation fails + */ public void continueSession() throws SessionControlException { resumeSession(getSessionId()); } + /** + * Resumes a session with the specified ID. + * + * @param sessionId The ID of the session to resume + * @throws SessionControlException if the operation fails + */ public void resumeSession(String sessionId) throws SessionControlException { if (StringUtils.isNotBlank(sessionId)) { transport.getTransportOptions().setResumeSessionId(sessionId); @@ -121,6 +184,14 @@ public class Session { this.start(); } + /** + * Sends a prompt to the CLI and processes the response. + * + * @param prompt The prompt to send to the CLI + * @param sessionEventConsumers Consumers for handling different types of events + * @throws SessionSendPromptException if sending the prompt fails + * @throws SessionControlException if a control operation fails + */ public void sendPrompt(String prompt, SessionEventConsumers sessionEventConsumers) throws SessionSendPromptException, SessionControlException { checkAvailable(); try { @@ -137,7 +208,8 @@ public class Session { Optional.ofNullable(sessionEventConsumers.onAssistantMessageTimeout(this)).orElse(defaultEventTimeout)); return false; } else if ("stream_event".equals(messageType)) { - MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onPartialAssistantMessage(this, jsonObject.to(SDKPartialAssistantMessage.class)), + MyConcurrentUtils.runAndWait( + () -> sessionEventConsumers.onPartialAssistantMessage(this, jsonObject.to(SDKPartialAssistantMessage.class)), Optional.ofNullable(sessionEventConsumers.onPartialAssistantMessageTimeout(this)).orElse(defaultEventTimeout)); return false; } else if ("user".equals(messageType)) { @@ -234,14 +306,29 @@ public class Session { return false; } + /** + * Gets the current session ID. + * + * @return The session ID, or null if not available + */ public String getSessionId() { return Optional.ofNullable(lastSdkSystemMessage).map(SDKSystemMessage::getSessionId).orElse(null); } + /** + * Checks if the session is available for operations. + * + * @return true if the session is available, false otherwise + */ public boolean isAvailable() { return transport.isAvailable(); } + /** + * Gets the capabilities of the CLI. + * + * @return A Capabilities object representing the CLI's capabilities + */ public Capabilities getCapabilities() { return Optional.ofNullable(lastCliControlInitializeResponse).map(CLIControlInitializeResponse::getCapabilities).orElse(new Capabilities()); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java new file mode 100644 index 000000000..3338c4ac7 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java @@ -0,0 +1,62 @@ +package com.alibaba.qwen.code.cli.session.event; + +import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +import com.alibaba.qwen.code.cli.session.Session; + +/** + * Interface for handling different types of assistant content during a session. + */ +public interface AssistantContentConsumers { + /** + * Handles text content from the assistant. + * + * @param session The session + * @param textAssistantContent The text content from the assistant + */ + void onText(Session session, TextAssistantContent textAssistantContent); + + /** + * Handles thinking content from the assistant. + * + * @param session The session + * @param thingkingAssistantContent The thinking content from the assistant + */ + void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent); + + /** + * Handles tool use content from the assistant. + * + * @param session The session + * @param toolUseAssistantContent The tool use content from the assistant + */ + void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent); + + /** + * Handles tool result content from the assistant. + * + * @param session The session + * @param toolResultAssistantContent The tool result content from the assistant + */ + void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent); + + /** + * Handles other types of assistant content. + * + * @param session The session + * @param other The other content from the assistant + */ + void onOtherContent(Session session, AssistantContent other); + + /** + * Handles usage information from the assistant. + * + * @param session The session + * @param AssistantUsage The usage information from the assistant + */ + void onUsage(Session session, AssistantUsage AssistantUsage); +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java new file mode 100644 index 000000000..f5c390a59 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java @@ -0,0 +1,38 @@ +package com.alibaba.qwen.code.cli.session.event; + +import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +import com.alibaba.qwen.code.cli.session.Session; + +/** + * Simple implementation of AssistantContentConsumers that provides empty implementations for all methods. + */ +public class AssistantContentSimpleConsumers implements AssistantContentConsumers { + @Override + public void onText(Session session, TextAssistantContent textAssistantContent) { + } + + @Override + public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { + } + + @Override + public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) { + } + + @Override + public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) { + } + + @Override + public void onOtherContent(Session session, AssistantContent other) { + } + + @Override + public void onUsage(Session session, AssistantUsage AssistantUsage) { + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java index 686620cfa..da861ee8b 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java @@ -12,40 +12,153 @@ 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; +/** + * Interface for handling different types of events during a session. + */ public interface SessionEventConsumers { + /** + * Handles system messages. + * + * @param session The session + * @param systemMessage The system message + */ void onSystemMessage(Session session, SDKSystemMessage systemMessage); + /** + * Handles result messages. + * + * @param session The session + * @param resultMessage The result message + */ void onResultMessage(Session session, SDKResultMessage resultMessage); + /** + * Handles assistant messages. + * + * @param session The session + * @param assistantMessage The assistant message + */ void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage); + /** + * Handles partial assistant messages. + * + * @param session The session + * @param partialAssistantMessage The partial assistant message + */ void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage); + /** + * Handles user messages. + * + * @param session The session + * @param userMessage The user message + */ void onUserMessage(Session session, SDKUserMessage userMessage); + /** + * Handles other types of messages. + * + * @param session The session + * @param message The message + */ void onOtherMessage(Session session, String message); + /** + * Handles control responses. + * + * @param session The session + * @param cliControlResponse The control response + */ void onControlResponse(Session session, CLIControlResponse cliControlResponse); + /** + * Handles control requests. + * + * @param session The session + * @param cliControlRequest The control request + * @return The control response + */ CLIControlResponse onControlRequest(Session session, CLIControlRequest cliControlRequest); + /** + * Handles permission requests. + * + * @param session The session + * @param permissionRequest The permission request + * @return The behavior for the permission request + */ Behavior onPermissionRequest(Session session, CLIControlRequest permissionRequest); + /** + * Gets timeout for system message handling. + * + * @param session The session + * @return The timeout for system message handling + */ Timeout onSystemMessageTimeout(Session session); + /** + * Gets timeout for result message handling. + * + * @param session The session + * @return The timeout for result message handling + */ Timeout onResultMessageTimeout(Session session); + /** + * Gets timeout for assistant message handling. + * + * @param session The session + * @return The timeout for assistant message handling + */ Timeout onAssistantMessageTimeout(Session session); + /** + * Gets timeout for partial assistant message handling. + * + * @param session The session + * @return The timeout for partial assistant message handling + */ Timeout onPartialAssistantMessageTimeout(Session session); + /** + * Gets timeout for user message handling. + * + * @param session The session + * @return The timeout for user message handling + */ Timeout onUserMessageTimeout(Session session); + /** + * Gets timeout for other message handling. + * + * @param session The session + * @return The timeout for other message handling + */ Timeout onOtherMessageTimeout(Session session); + /** + * Gets timeout for control response handling. + * + * @param session The session + * @return The timeout for control response handling + */ Timeout onControlResponseTimeout(Session session); + /** + * Gets timeout for control request handling. + * + * @param session The session + * @return The timeout for control request handling + */ Timeout onControlRequestTimeout(Session session); + /** + * Gets timeout for permission request handling. + * + * @param session The session + * @return The timeout for permission request handling + */ Timeout onPermissionRequestTimeout(Session session); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java index fe21bbe96..517cc022c 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java @@ -1,12 +1,13 @@ 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.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow; import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation; @@ -16,6 +17,7 @@ import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage; import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage; import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKPartialAssistantMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock; import com.alibaba.qwen.code.cli.protocol.message.assistant.event.ContentBlockDeltaEvent; import com.alibaba.qwen.code.cli.protocol.message.assistant.event.StreamEvent; import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlPermissionRequest; @@ -24,6 +26,12 @@ 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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple implementation of SessionEventConsumers that provides basic implementations for all methods. + */ public class SessionEventSimpleConsumers implements SessionEventConsumers { @Override public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { @@ -35,22 +43,42 @@ public class SessionEventSimpleConsumers implements SessionEventConsumers { @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); + List> contentBlocks = assistantMessage.getMessage().getContent(); + if (assistantContentConsumers == null || contentBlocks == null || contentBlocks.isEmpty()) { + return; + } + assistantContentConsumers.onUsage(session, new AssistantUsage(assistantMessage.getMessage().getId(), assistantMessage.getMessage().getUsage())); + + if (!session.isStreaming()) { + contentBlocks.forEach(contentBlock -> consumeAssistantContent(session, contentBlock)); + } } @Override public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) { StreamEvent event = partialAssistantMessage.getEvent(); if (!(event instanceof ContentBlockDeltaEvent)) { + log.debug("received partialAssistantMessage and is not instance of ContentBlockDeltaEvent, will ignore process. the message is {}", + partialAssistantMessage); return; } - onAssistantMessageIncludePartial(session, Collections.singletonList(((ContentBlockDeltaEvent) event).getDelta()), AssistantMessageOutputType.partial); + ContentBlockDeltaEvent contentBlockDeltaEvent = (ContentBlockDeltaEvent) event; + contentBlockDeltaEvent.getDelta().setMessageId(partialAssistantMessage.getMessageId()); + consumeAssistantContent(session, contentBlockDeltaEvent.getDelta()); } - public void onAssistantMessageIncludePartial(Session session, List assistantContents, - AssistantMessageOutputType assistantMessageOutputType) { + protected void consumeAssistantContent(Session session, AssistantContent assistantContent) { + if (assistantContent instanceof TextAssistantContent) { + assistantContentConsumers.onText(session, (TextAssistantContent) assistantContent); + } else if (assistantContent instanceof ThingkingAssistantContent) { + assistantContentConsumers.onThinking(session, (ThingkingAssistantContent) assistantContent); + } else if (assistantContent instanceof ToolUseAssistantContent) { + assistantContentConsumers.onToolUse(session, (ToolUseAssistantContent) assistantContent); + } else if (assistantContent instanceof ToolResultAssistantContent) { + assistantContentConsumers.onToolResult(session, (ToolResultAssistantContent) assistantContent); + } else { + assistantContentConsumers.onOtherContent(session, assistantContent); + } } @Override @@ -124,37 +152,88 @@ public class SessionEventSimpleConsumers implements SessionEventConsumers { return defaultEventTimeout; } - public Timeout getDefaultEventTimeout() { + /** + * Gets the default event timeout. + * + * @return The default event timeout + */ + protected Timeout getDefaultEventTimeout() { return defaultEventTimeout; } + /** + * Sets the default event timeout. + * + * @param defaultEventTimeout The default event timeout + * @return This instance for method chaining + */ public SessionEventSimpleConsumers setDefaultEventTimeout(Timeout defaultEventTimeout) { this.defaultEventTimeout = defaultEventTimeout; return this; } - public Operation getDefaultPermissionOperation() { + /** + * Gets the default permission operation. + * + * @return The default permission operation + */ + protected Operation getDefaultPermissionOperation() { return defaultPermissionOperation; } + /** + * Sets the default permission operation. + * + * @param defaultPermissionOperation The default permission operation + * @return This instance for method chaining + */ public SessionEventSimpleConsumers setDefaultPermissionOperation(Operation defaultPermissionOperation) { this.defaultPermissionOperation = defaultPermissionOperation; return this; } + /** + * Creates a new SessionEventSimpleConsumers instance with default values. + */ public SessionEventSimpleConsumers() { } - public SessionEventSimpleConsumers(Operation defaultPermissionOperation, Timeout defaultEventTimeout) { + /** + * Creates a new SessionEventSimpleConsumers instance with the specified parameters. + * + * @param defaultPermissionOperation The default permission operation + * @param defaultEventTimeout The default event timeout + * @param assistantContentConsumers The assistant content consumers + */ + public SessionEventSimpleConsumers(Operation defaultPermissionOperation, Timeout defaultEventTimeout, + AssistantContentConsumers assistantContentConsumers) { this.defaultPermissionOperation = defaultPermissionOperation; this.defaultEventTimeout = defaultEventTimeout; + this.assistantContentConsumers = assistantContentConsumers; } + /** + * The default permission operation. + */ private Operation defaultPermissionOperation = Operation.deny; + /** + * The default event timeout. + */ protected Timeout defaultEventTimeout = Timeout.TIMEOUT_60_SECONDS; + /** + * The assistant content consumers. + */ + protected AssistantContentConsumers assistantContentConsumers; + private static final Logger log = LoggerFactory.getLogger(SessionEventSimpleConsumers.class); - public enum AssistantMessageOutputType { - entire, - partial + /** + * Sets the assistant content consumers. + * + * @param assistantContentConsumers The assistant content consumers + * @return This instance for method chaining + */ + public SessionEventSimpleConsumers setBlockConsumer(AssistantContentConsumers assistantContentConsumers) { + this.assistantContentConsumers = assistantContentConsumers; + return this; } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java index 770d5982c..3fa30bd55 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java @@ -1,21 +1,51 @@ package com.alibaba.qwen.code.cli.session.exception; +/** + * Exception thrown when a session control operation fails. + */ public class SessionControlException extends Exception { + /** + * Creates a new exception. + */ public SessionControlException() { } + /** + * Creates a new exception with a message. + * + * @param message The exception message + */ public SessionControlException(String message) { super(message); } + /** + * Creates a new exception with a message and cause. + * + * @param message The exception message + * @param cause The exception cause + */ public SessionControlException(String message, Throwable cause) { super(message, cause); } + /** + * Creates a new exception with a cause. + * + * @param cause The exception cause + */ public SessionControlException(Throwable cause) { super(cause); } + /** + * Creates a new exception with all parameters. + * + * @param message The exception message + * @param cause The exception cause + * @param enableSuppression Whether suppression is enabled + * @param writableStackTrace Whether the stack trace is writable + */ public SessionControlException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java index 74de3bba7..460c5f560 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java @@ -1,21 +1,51 @@ package com.alibaba.qwen.code.cli.session.exception; +/** + * Exception thrown when sending a prompt in a session fails. + */ public class SessionSendPromptException extends Exception { + /** + * Creates a new exception. + */ public SessionSendPromptException() { } + /** + * Creates a new exception with a message. + * + * @param message The exception message + */ public SessionSendPromptException(String message) { super(message); } + /** + * Creates a new exception with a message and cause. + * + * @param message The exception message + * @param cause The exception cause + */ public SessionSendPromptException(String message, Throwable cause) { super(message, cause); } + /** + * Creates a new exception with a cause. + * + * @param cause The exception cause + */ public SessionSendPromptException(Throwable cause) { super(cause); } + /** + * Creates a new exception with all parameters. + * + * @param message The exception message + * @param cause The exception cause + * @param enableSuppression Whether suppression is enabled + * @param writableStackTrace Whether the stack trace is writable + */ public SessionSendPromptException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java index af4266f45..1bb46020e 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java @@ -5,20 +5,71 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.function.Function; +/** + * Defines the contract for communication with the Qwen Code CLI. + */ public interface Transport { + /** + * Gets the transport options used by this transport. + * + * @return The transport options + */ TransportOptions getTransportOptions(); + /** + * Checks if the transport is currently reading. + * + * @return true if reading, false otherwise + */ boolean isReading(); + /** + * Starts the transport. + * + * @throws IOException if starting fails + */ void start() throws IOException; + /** + * Closes the transport and releases resources. + * + * @throws IOException if closing fails + */ void close() throws IOException; + /** + * Checks if the transport is available for communication. + * + * @return true if available, false otherwise + */ boolean isAvailable(); + /** + * Sends a message and waits for a single-line response. + * + * @param message The message to send + * @return The response message + * @throws IOException if an I/O error occurs + * @throws ExecutionException if an execution error occurs + * @throws InterruptedException if the operation is interrupted + * @throws TimeoutException if the operation times out + */ String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException; + /** + * Sends a message and waits for a multi-line response. + * + * @param message The message to send + * @param callBackFunction A function to process each line of the response + * @throws IOException if an I/O error occurs + */ void inputWaitForMultiLine(String message, Function callBackFunction) throws IOException; + /** + * Sends a message without waiting for a response. + * + * @param message The message to send + * @throws IOException if an I/O error occurs + */ void inputNoWaitResponse(String message) throws IOException; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java index 7e274d1a0..a73f1c13d 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java @@ -6,163 +6,390 @@ import java.util.Map; import com.alibaba.qwen.code.cli.protocol.data.PermissionMode; import com.alibaba.qwen.code.cli.utils.Timeout; +/** + * Configuration options for the transport layer. + */ public class TransportOptions implements Cloneable { + /** + * Path to the Qwen executable. + */ private String pathToQwenExecutable; + /** + * Current working directory for the CLI process. + */ private String cwd; + /** + * Model to use for the session. + */ private String model; + /** + * Permission mode for the session. + */ private PermissionMode permissionMode; + /** + * Environment variables to pass to the CLI process. + */ private Map env; + /** + * Maximum number of turns in a session. + */ private Integer maxSessionTurns; + /** + * List of core tools to enable. + */ private List coreTools; + /** + * List of tools to exclude. + */ private List excludeTools; + /** + * List of tools that are allowed. + */ private List allowedTools; + /** + * Authentication type to use. + */ private String authType; + /** + * Whether to include partial messages in responses. + */ private Boolean includePartialMessages; + /** + * Whether to enable skills. + */ private Boolean skillsEnable; + /** + * Timeout for individual turns. + */ private Timeout turnTimeout; + /** + * Timeout for messages. + */ private Timeout messageTimeout; + /** + * Session ID to resume. + */ private String resumeSessionId; + /** + * Additional options to pass to the CLI. + */ private List otherOptions; + /** + * Gets the path to the Qwen executable. + * + * @return The path to the Qwen executable + */ public String getPathToQwenExecutable() { return pathToQwenExecutable; } + /** + * Sets the path to the Qwen executable. + * + * @param pathToQwenExecutable The path to the Qwen executable + * @return This instance for method chaining + */ public TransportOptions setPathToQwenExecutable(String pathToQwenExecutable) { this.pathToQwenExecutable = pathToQwenExecutable; return this; } + /** + * Gets the current working directory. + * + * @return The current working directory + */ public String getCwd() { return cwd; } + /** + * Sets the current working directory. + * + * @param cwd The current working directory + * @return This instance for method chaining + */ public TransportOptions setCwd(String cwd) { this.cwd = cwd; return this; } + /** + * Gets the model to use. + * + * @return The model name + */ public String getModel() { return model; } + /** + * Sets the model to use. + * + * @param model The model name + * @return This instance for method chaining + */ public TransportOptions setModel(String model) { this.model = model; return this; } + /** + * Gets the permission mode. + * + * @return The permission mode + */ public PermissionMode getPermissionMode() { return permissionMode; } + /** + * Sets the permission mode. + * + * @param permissionMode The permission mode + * @return This instance for method chaining + */ public TransportOptions setPermissionMode(PermissionMode permissionMode) { this.permissionMode = permissionMode; return this; } + /** + * Gets the environment variables. + * + * @return A map of environment variables + */ public Map getEnv() { return env; } + /** + * Sets the environment variables. + * + * @param env A map of environment variables + * @return This instance for method chaining + */ public TransportOptions setEnv(Map env) { this.env = env; return this; } + /** + * Gets the maximum number of session turns. + * + * @return The maximum number of session turns + */ public Integer getMaxSessionTurns() { return maxSessionTurns; } + /** + * Sets the maximum number of session turns. + * + * @param maxSessionTurns The maximum number of session turns + * @return This instance for method chaining + */ public TransportOptions setMaxSessionTurns(Integer maxSessionTurns) { this.maxSessionTurns = maxSessionTurns; return this; } + /** + * Gets the list of core tools. + * + * @return The list of core tools + */ public List getCoreTools() { return coreTools; } + /** + * Sets the list of core tools. + * + * @param coreTools The list of core tools + * @return This instance for method chaining + */ public TransportOptions setCoreTools(List coreTools) { this.coreTools = coreTools; return this; } + /** + * Gets the list of excluded tools. + * + * @return The list of excluded tools + */ public List getExcludeTools() { return excludeTools; } + /** + * Sets the list of excluded tools. + * + * @param excludeTools The list of excluded tools + * @return This instance for method chaining + */ public TransportOptions setExcludeTools(List excludeTools) { this.excludeTools = excludeTools; return this; } + /** + * Gets the list of allowed tools. + * + * @return The list of allowed tools + */ public List getAllowedTools() { return allowedTools; } + /** + * Sets the list of allowed tools. + * + * @param allowedTools The list of allowed tools + * @return This instance for method chaining + */ public TransportOptions setAllowedTools(List allowedTools) { this.allowedTools = allowedTools; return this; } + /** + * Gets the authentication type. + * + * @return The authentication type + */ public String getAuthType() { return authType; } + /** + * Sets the authentication type. + * + * @param authType The authentication type + * @return This instance for method chaining + */ public TransportOptions setAuthType(String authType) { this.authType = authType; return this; } + /** + * Gets whether to include partial messages. + * + * @return Whether to include partial messages + */ public Boolean getIncludePartialMessages() { return includePartialMessages; } + /** + * Sets whether to include partial messages. + * + * @param includePartialMessages Whether to include partial messages + * @return This instance for method chaining + */ public TransportOptions setIncludePartialMessages(Boolean includePartialMessages) { this.includePartialMessages = includePartialMessages; return this; } + /** + * Gets whether skills are enabled. + * + * @return Whether skills are enabled + */ public Boolean getSkillsEnable() { return skillsEnable; } + /** + * Sets whether skills are enabled. + * + * @param skillsEnable Whether skills are enabled + * @return This instance for method chaining + */ public TransportOptions setSkillsEnable(Boolean skillsEnable) { this.skillsEnable = skillsEnable; return this; } + /** + * Gets the turn timeout. + * + * @return The turn timeout + */ public Timeout getTurnTimeout() { return turnTimeout; } + /** + * Sets the turn timeout. + * + * @param turnTimeout The turn timeout + * @return This instance for method chaining + */ public TransportOptions setTurnTimeout(Timeout turnTimeout) { this.turnTimeout = turnTimeout; return this; } + /** + * Gets the message timeout. + * + * @return The message timeout + */ public Timeout getMessageTimeout() { return messageTimeout; } + /** + * Sets the message timeout. + * + * @param messageTimeout The message timeout + * @return This instance for method chaining + */ public TransportOptions setMessageTimeout(Timeout messageTimeout) { this.messageTimeout = messageTimeout; return this; } + /** + * Gets the session ID to resume. + * + * @return The session ID to resume + */ public String getResumeSessionId() { return resumeSessionId; } + /** + * Sets the session ID to resume. + * + * @param resumeSessionId The session ID to resume + * @return This instance for method chaining + */ public TransportOptions setResumeSessionId(String resumeSessionId) { this.resumeSessionId = resumeSessionId; return this; } + /** + * Gets additional options. + * + * @return Additional options + */ public List getOtherOptions() { return otherOptions; } + /** + * Sets additional options. + * + * @param otherOptions Additional options + * @return This instance for method chaining + */ public TransportOptions setOtherOptions(List otherOptions) { this.otherOptions = otherOptions; return this; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java index 11be50ecc..fb590e9c0 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java @@ -22,6 +22,9 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Function; +/** + * Implementation of the Transport interface that communicates with the Qwen CLI via a process. + */ public class ProcessTransport implements Transport { private static final Logger log = LoggerFactory.getLogger(ProcessTransport.class); private final TransportOptions transportOptions; @@ -36,14 +39,32 @@ public class ProcessTransport implements Transport { private final AtomicBoolean reading = new AtomicBoolean(false); + /** + * Constructs a new ProcessTransport with default options. + * + * @throws IOException if starting the process fails + */ public ProcessTransport() throws IOException { this(new TransportOptions()); } + /** + * Constructs a new ProcessTransport with the specified options. + * + * @param transportOptions The transport options to use + * @throws IOException if starting the process fails + */ public ProcessTransport(TransportOptions transportOptions) throws IOException { this(transportOptions, (line) -> log.error("process error: {}", line)); } + /** + * Constructs a new ProcessTransport with the specified options and error handler. + * + * @param transportOptions The transport options to use + * @param errorHandler The error handler to use + * @throws IOException if starting the process fails + */ public ProcessTransport(TransportOptions transportOptions, Consumer errorHandler) throws IOException { this.transportOptions = transportOptions; this.errorHandler = errorHandler; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java index ba9289181..fe8f21691 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java @@ -14,23 +14,55 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; +/** + * Adapter that converts TransportOptions to command-line arguments for the CLI process. + */ class TransportOptionsAdapter { + /** + * The adapted transport options. + */ TransportOptions transportOptions; + /** + * Default timeout for turns. + */ private static final Timeout DEFAULT_TURN_TIMEOUT = new Timeout(1000 * 60 * 30L, TimeUnit.MILLISECONDS); + /** + * Default timeout for messages. + */ private static final Timeout DEFAULT_MESSAGE_TIMEOUT = new Timeout(1000 * 60 * 3L, TimeUnit.MILLISECONDS); + /** + * Constructs a new adapter with the specified options. + * + * @param userTransportOptions The user's transport options + */ TransportOptionsAdapter(TransportOptions userTransportOptions) { transportOptions = addDefaultTransportOptions(userTransportOptions); } + /** + * Gets the processed transport options. + * + * @return The processed transport options + */ TransportOptions getHandledTransportOptions() { return transportOptions; } + /** + * Gets the current working directory. + * + * @return The current working directory + */ String getCwd() { return transportOptions.getCwd(); } + /** + * Builds command-line arguments from the transport options. + * + * @return An array of command-line arguments + */ String[] buildCommandArgs() { List args = new ArrayList<>( Arrays.asList(transportOptions.getPathToQwenExecutable(), "--input-format", "stream-json", "--output-format", @@ -90,6 +122,12 @@ class TransportOptionsAdapter { return args.toArray(new String[] {}); } + /** + * Adds default values to the user's transport options. + * + * @param userTransportOptions The user's transport options + * @return The options with defaults added + */ private TransportOptions addDefaultTransportOptions(TransportOptions userTransportOptions) { TransportOptions transportOptions = Optional.ofNullable(userTransportOptions) .map(TransportOptions::clone) diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java index 3b5cf22da..d50892701 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java @@ -9,9 +9,18 @@ import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Utility class for concurrent operations. + */ public class MyConcurrentUtils { private static final Logger log = LoggerFactory.getLogger(MyConcurrentUtils.class); + /** + * Runs a task and waits for it to complete with a timeout. + * + * @param runnable The task to run + * @param timeOut The timeout for the operation + */ public static void runAndWait(Runnable runnable, Timeout timeOut) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { @@ -34,6 +43,17 @@ public class MyConcurrentUtils { } } + /** + * Runs a task that returns a value and waits for it to complete with a timeout. + * + * @param supplier The task to run + * @param timeOut The timeout for the operation + * @param The type of the result + * @return The result of the task + * @throws ExecutionException if an execution error occurs + * @throws InterruptedException if the operation is interrupted + * @throws TimeoutException if the operation times out + */ public static T runAndWait(Supplier supplier, Timeout timeOut) throws ExecutionException, InterruptedException, TimeoutException { CompletableFuture future = CompletableFuture.supplyAsync(() -> { @@ -52,6 +72,12 @@ public class MyConcurrentUtils { } } + /** + * Runs a task asynchronously with an error callback. + * + * @param runnable The task to run + * @param errorCallback The error callback + */ public static void asyncRun(Runnable runnable, BiConsumer errorCallback) { CompletableFuture future = CompletableFuture.runAsync(() -> { try { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java index 9837ef798..671bdde74 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java @@ -9,6 +9,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +/** + * Configuration for the thread pool used by the SDK. + */ public class ThreadPoolConfig { private static final ThreadPoolExecutor defaultExecutor = new ThreadPoolExecutor( 10, 30, 60L, TimeUnit.SECONDS, @@ -27,10 +30,21 @@ public class ThreadPoolConfig { ); private static Supplier executorSupplier; + + /** + * Sets the supplier for the executor. + * + * @param executorSupplier The supplier for the executor + */ public static void setExecutorSupplier(Supplier executorSupplier) { ThreadPoolConfig.executorSupplier = executorSupplier; } + /** + * Gets the default executor. + * + * @return The default executor + */ public static ThreadPoolExecutor getDefaultExecutor() { return defaultExecutor; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java index f00b39085..76fb29ab8 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java @@ -4,9 +4,25 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.Validate; +/** + * Represents a timeout value with a time unit. + */ public class Timeout { + /** + * The timeout value. + */ private final Long value; + /** + * The time unit. + */ private final TimeUnit unit; + + /** + * Creates a new Timeout instance. + * + * @param value The timeout value + * @param unit The time unit + */ public Timeout(Long value, TimeUnit unit) { Validate.notNull(value, "value can not be null"); Validate.notNull(unit, "unit can not be null"); @@ -14,14 +30,30 @@ public class Timeout { this.unit = unit; } + /** + * Gets the timeout value. + * + * @return The timeout value + */ public Long getValue() { return value; } + /** + * Gets the time unit. + * + * @return The time unit + */ public TimeUnit getUnit() { return unit; } + /** + * A timeout of 60 seconds. + */ public static final Timeout TIMEOUT_60_SECONDS = new Timeout(60L, TimeUnit.SECONDS); + /** + * A timeout of 30 minutes. + */ public static final Timeout TIMEOUT_30_MINUTES = new Timeout(60L, TimeUnit.MINUTES); } diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCodeCliTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCodeCliTest.java index 51be8bf4c..ca10173f7 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCodeCliTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/QwenCodeCliTest.java @@ -2,6 +2,8 @@ package com.alibaba.qwen.code.cli; import java.util.List; +import com.alibaba.qwen.code.cli.transport.TransportOptions; + import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,7 +16,14 @@ class QwenCodeCliTest { @Test void simpleQuery() { List result = QwenCodeCli.simpleQuery("hello world"); - log.info("result: {}", result); + log.info("simpleQuery result: {}", result); + assertNotNull(result); + } + + @Test + void simpleQueryWithModel() { + List result = QwenCodeCli.simpleQuery("hello world", new TransportOptions().setModel("qwen-plus")); + log.info("simpleQueryWithModel result: {}", result); assertNotNull(result); } } diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java index 9292d83fe..a6463d41d 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java @@ -1,13 +1,16 @@ 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.AssistantUsage; import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; import com.alibaba.qwen.code.cli.protocol.data.PermissionMode; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow; import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation; @@ -17,6 +20,7 @@ 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.AssistantContentConsumers; 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; @@ -34,19 +38,43 @@ class SessionTest { private static final Logger log = LoggerFactory.getLogger(SessionTest.class); @Test - void partialSendPromptSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { + void partialSendPromptSuccessfully() throws SessionControlException, SessionSendPromptException { Session session = QwenCodeCli.newSession(new TransportOptions().setIncludePartialMessages(true)); session.sendPrompt("in the dir src/test/temp/, create file empty file test.touch", new SessionEventSimpleConsumers() { + }.setDefaultPermissionOperation(Operation.allow).setBlockConsumer(new AssistantContentConsumers() { @Override - public void onAssistantMessageIncludePartial(Session session, List assistantContents, - AssistantMessageOutputType assistantMessageOutputType) { - log.info("onAssistantMessageIncludePartial: {}", JSON.toJSONString(assistantContents)); + public void onText(Session session, TextAssistantContent textAssistantContent) { + log.info("receive textAssistantContent {}", textAssistantContent); } - }.setDefaultPermissionOperation(Operation.allow)); + + @Override + public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { + log.info("receive thingkingAssistantContent {}", thingkingAssistantContent); + } + + @Override + public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) { + log.info("receive toolUseAssistantContent {}", toolUseAssistantContent); + } + + @Override + public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) { + log.info("receive toolResultAssistantContent {}", toolResultAssistantContent); + } + + public void onOtherContent(Session session, AssistantContent other) { + log.info("receive otherContent {}", other); + } + + @Override + public void onUsage(Session session, AssistantUsage assistantUsage) { + log.info("receive assistantUsage {}", assistantUsage); + } + })); } @Test - void setPermissionModeSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { + void setPermissionModeSuccessfully() throws SessionControlException, SessionSendPromptException { Session session = QwenCodeCli.newSession(new TransportOptions()); log.info(session.setPermissionMode(PermissionMode.YOLO).map(s -> s ? "setPermissionMode 1 success" : "setPermissionMode 1 error") @@ -72,7 +100,7 @@ class SessionTest { } @Test - void sendPromptAndSetModelSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { + void sendPromptAndSetModelSuccessfully() throws SessionControlException, SessionSendPromptException { Session session = QwenCodeCli.newSession(new TransportOptions()); log.info(session.setModel("qwen3-coder-flash").map(s -> s ? "setModel 1 success" : "setModel 1 error").orElse("setModel 1 unknown")); @@ -97,7 +125,7 @@ class SessionTest { } @Test - void sendPromptAndInterruptContinueSuccessfully() throws IOException, SessionControlException, SessionSendPromptException { + void sendPromptAndInterruptContinueSuccessfully() throws SessionControlException, SessionSendPromptException { Session session = QwenCodeCli.newSession(); SessionEventConsumers sessionEventConsumers = new SessionEventSimpleConsumers() { From 32e8b01cf0a18eb7e1a84ad908a40a3461f527ee Mon Sep 17 00:00:00 2001 From: skyfire Date: Sun, 4 Jan 2026 19:39:00 +0800 Subject: [PATCH 40/65] for javadoc --- .../alibaba/qwen/code/cli/QwenCodeCli.java | 3 +++ .../cli/protocol/data/AssistantContent.java | 2 ++ .../cli/protocol/data/AssistantUsage.java | 8 ++++++ .../protocol/data/CLIPermissionDenial.java | 3 +++ .../code/cli/protocol/data/Capabilities.java | 3 +++ .../code/cli/protocol/data/ExtendedUsage.java | 3 +++ .../cli/protocol/data/InitializeConfig.java | 3 +++ .../code/cli/protocol/data/ModelUsage.java | 3 +++ .../cli/protocol/data/PermissionMode.java | 3 +++ .../qwen/code/cli/protocol/data/Usage.java | 8 ++++++ .../cli/protocol/data/behavior/Allow.java | 3 +++ .../cli/protocol/data/behavior/Behavior.java | 3 +++ .../code/cli/protocol/data/behavior/Deny.java | 3 +++ .../code/cli/protocol/message/Message.java | 3 +++ .../cli/protocol/message/MessageBase.java | 10 +++++++ .../protocol/message/SDKResultMessage.java | 3 +++ .../protocol/message/SDKSystemMessage.java | 3 +++ .../cli/protocol/message/SDKUserMessage.java | 3 +++ .../assistant/APIAssistantMessage.java | 3 +++ .../assistant/SDKAssistantMessage.java | 4 +++ .../assistant/SDKPartialAssistantMessage.java | 3 +++ .../message/assistant/block/Annotation.java | 3 +++ .../message/assistant/block/ContentBlock.java | 9 +++++++ .../message/assistant/block/TextBlock.java | 4 +++ .../assistant/block/ThinkingBlock.java | 4 +++ .../assistant/block/ToolResultBlock.java | 4 +++ .../message/assistant/block/ToolUseBlock.java | 13 +++++++-- .../event/ContentBlockDeltaEvent.java | 3 +++ .../event/ContentBlockStartEvent.java | 3 +++ .../event/ContentBlockStopEvent.java | 3 +++ .../event/MessageStartStreamEvent.java | 3 +++ .../event/MessageStopStreamEvent.java | 3 +++ .../message/assistant/event/StreamEvent.java | 3 +++ .../control/CLIControlInitializeRequest.java | 3 +++ .../control/CLIControlInitializeResponse.java | 3 +++ .../control/CLIControlInterruptRequest.java | 3 +++ .../control/CLIControlPermissionRequest.java | 3 +++ .../control/CLIControlPermissionResponse.java | 3 +++ .../message/control/CLIControlRequest.java | 2 ++ .../message/control/CLIControlResponse.java | 2 ++ .../control/CLIControlSetModelRequest.java | 3 +++ .../control/CLIControlSetModelResponse.java | 3 +++ .../CLIControlSetPermissionModeRequest.java | 3 +++ .../qwen/code/cli/session/Session.java | 23 +++++++++------- .../event/AssistantContentConsumers.java | 3 +++ .../AssistantContentSimpleConsumers.java | 9 +++++++ .../session/event/SessionEventConsumers.java | 3 +++ .../event/SessionEventSimpleConsumers.java | 27 +++++++++++++++++++ .../exception/SessionControlException.java | 3 +++ .../exception/SessionSendPromptException.java | 3 +++ .../qwen/code/cli/transport/Transport.java | 19 +++++++------ .../code/cli/transport/TransportOptions.java | 4 +++ .../transport/process/ProcessTransport.java | 17 +++++++++--- .../code/cli/utils/MyConcurrentUtils.java | 9 ++++--- .../qwen/code/cli/utils/ThreadPoolConfig.java | 3 +++ .../alibaba/qwen/code/cli/utils/Timeout.java | 3 +++ 56 files changed, 263 insertions(+), 26 deletions(-) diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java index adbb235e6..6571f5de3 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java @@ -25,6 +25,9 @@ import org.slf4j.LoggerFactory; /** * Main entry point for interacting with the Qwen Code CLI. Provides static methods for simple queries and session management. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class QwenCodeCli { private static final Logger log = LoggerFactory.getLogger(QwenCodeCli.class); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java index d0414a83f..ba0356545 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantContent.java @@ -6,6 +6,8 @@ import java.util.Map; * Represents content from the assistant in a Qwen Code session. * * @param The type of content + * @author skyfire + * @version $Id: 0.0.1 */ public interface AssistantContent { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantUsage.java index 261898796..8ecb2a5bf 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantUsage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/AssistantUsage.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.JSON; /** * Represents usage information for an assistant message. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class AssistantUsage { /** @@ -62,6 +65,11 @@ public class AssistantUsage { this.usage = usage; } + /** + *

toString.

+ * + * @return a {@link java.lang.String} object. + */ public String toString() { return JSON.toJSONString(this); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java index 312344296..bc155b776 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/CLIPermissionDenial.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONField; /** * Represents a permission denial from the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class CLIPermissionDenial { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java index 2f22c0ce4..e6cbadfe9 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Capabilities.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONField; /** * Represents the capabilities of the Qwen Code CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class Capabilities { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java index a894f5b7a..7e67a629f 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ExtendedUsage.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONField; /** * Extends the Usage class with additional usage information. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class ExtendedUsage extends Usage { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java index c0858ee4d..36296d053 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/InitializeConfig.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.protocol.data; /** * Configuration for initializing the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class InitializeConfig { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java index d3286c8a3..33b426c4a 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/ModelUsage.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.protocol.data; /** * Represents usage information for a specific model. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class ModelUsage { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java index aafc69bef..420dd760d 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/PermissionMode.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.protocol.data; /** * Represents different permission modes for the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public enum PermissionMode { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java index a0e4b3009..7fb430511 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/Usage.java @@ -5,6 +5,9 @@ import com.alibaba.fastjson2.annotation.JSONField; /** * Represents usage information for a message. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class Usage { /** @@ -123,6 +126,11 @@ public class Usage { this.totalTokens = totalTokens; } + /** + *

toString.

+ * + * @return a {@link java.lang.String} object. + */ public String toString() { return JSON.toJSONString(this); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java index cc2f5d533..5ed08f099 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Allow.java @@ -6,6 +6,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Represents an allow behavior that permits an operation. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "operation", typeName = "allow") public class Allow extends Behavior { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java index 2ea1b6ff1..20893e044 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Base class for behavior objects that define how the CLI should handle requests. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "operation", typeName = "Behavior", seeAlso = {Allow.class, Deny.class}) public class Behavior { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java index d24560620..042673e45 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Deny.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Represents a deny behavior that rejects an operation. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "operation", typeName = "deny") public class Deny extends Behavior { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java index f816d7f2e..855fb5de7 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/Message.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.protocol.message; /** * Represents a message in the Qwen Code protocol. + * + * @author skyfire + * @version $Id: 0.0.1 */ public interface Message { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java index aa9cbfd1a..37390164a 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/MessageBase.java @@ -6,6 +6,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Base class for messages in the Qwen Code protocol. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(alphabetic = false, typeKey = "type", typeName = "MessageBase") public class MessageBase implements Message{ @@ -20,10 +23,16 @@ public class MessageBase implements Message{ @JSONField(name = "message_id") protected String messageId; + /** + *

toString.

+ * + * @return a {@link java.lang.String} object. + */ public String toString() { return JSON.toJSONString(this); } + /** {@inheritDoc} */ @Override public String getType() { return type; @@ -38,6 +47,7 @@ public class MessageBase implements Message{ this.type = type; } + /** {@inheritDoc} */ @Override public String getMessageId() { return messageId; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java index f96ecade7..58889630b 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKResultMessage.java @@ -11,6 +11,9 @@ import com.alibaba.qwen.code.cli.protocol.data.Usage; /** * Represents a result message from the SDK. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "result") public class SDKResultMessage extends MessageBase { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java index 4a61513d5..abdb0fe5b 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKSystemMessage.java @@ -8,6 +8,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Represents a system message from the SDK. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "system") public class SDKSystemMessage extends MessageBase { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java index bdd69f01d..e2d9f1e2a 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/SDKUserMessage.java @@ -7,6 +7,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Represents a user message in the SDK protocol. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "user") public class SDKUserMessage extends MessageBase { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java index b64952228..b54bca443 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/APIAssistantMessage.java @@ -8,6 +8,9 @@ import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock; /** * Represents an API assistant message. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class APIAssistantMessage { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java index efb6071cf..b94fde07b 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKAssistantMessage.java @@ -6,6 +6,9 @@ import com.alibaba.qwen.code.cli.protocol.message.MessageBase; /** * Represents an SDK assistant message. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "assistant") public class SDKAssistantMessage extends MessageBase { @@ -38,6 +41,7 @@ public class SDKAssistantMessage extends MessageBase { this.type = "assistant"; } + /** {@inheritDoc} */ @Override public String getMessageId() { return this.getUuid(); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java index 2c7cc0934..0ace1e3f0 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/SDKPartialAssistantMessage.java @@ -7,6 +7,9 @@ import com.alibaba.qwen.code.cli.protocol.message.assistant.event.StreamEvent; /** * Represents a partial assistant message during streaming. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "stream_event") public class SDKPartialAssistantMessage extends MessageBase { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java index e78cf3576..880bb12f8 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/Annotation.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONField; /** * Represents an annotation for a content block. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class Annotation { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java index c8c7284b0..fabee58b5 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ContentBlock.java @@ -10,6 +10,8 @@ import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; * Abstract base class for content blocks in assistant messages. * * @param The type of content + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "ContentBlock", seeAlso = { TextBlock.class, ToolResultBlock.class, ThinkingBlock.class, ToolUseBlock.class }) public abstract class ContentBlock implements AssistantContent { @@ -26,6 +28,7 @@ public abstract class ContentBlock implements AssistantContent { */ protected String messageId; + /** {@inheritDoc} */ @Override public String getType() { return type; @@ -58,6 +61,7 @@ public abstract class ContentBlock implements AssistantContent { this.annotations = annotations; } + /** {@inheritDoc} */ @Override public String getMessageId() { return messageId; @@ -72,6 +76,11 @@ public abstract class ContentBlock implements AssistantContent { this.messageId = messageId; } + /** + *

toString.

+ * + * @return a {@link java.lang.String} object. + */ public String toString() { return JSON.toJSONString(this); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java index 9980a74b8..5b6953fda 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/TextBlock.java @@ -5,6 +5,9 @@ import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantCon /** * Represents a text content block. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "text") public class TextBlock extends ContentBlock implements TextAssistantContent { @@ -31,6 +34,7 @@ public class TextBlock extends ContentBlock implements TextAssistantCont this.text = text; } + /** {@inheritDoc} */ @Override public String getContentOfAssistant() { return text; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java index 9b33730bc..52967e67b 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ThinkingBlock.java @@ -5,6 +5,9 @@ import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssista /** * Represents a thinking content block. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "thinking") public class ThinkingBlock extends ContentBlock implements ThingkingAssistantContent { @@ -53,6 +56,7 @@ public class ThinkingBlock extends ContentBlock implements ThingkingAssi this.signature = signature; } + /** {@inheritDoc} */ @Override public String getContentOfAssistant() { return thinking; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java index 43da74b0c..35185a40d 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolResultBlock.java @@ -6,6 +6,9 @@ import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssist /** * Represents a tool result content block. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "tool_result") public class ToolResultBlock extends ContentBlock implements ToolResultAssistantContent { @@ -81,6 +84,7 @@ public class ToolResultBlock extends ContentBlock implements ToolResultA this.isError = isError; } + /** {@inheritDoc} */ @Override public String getContentOfAssistant() { return content; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java index da3624a67..91cfacb36 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/block/ToolUseBlock.java @@ -9,6 +9,9 @@ import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistant /** * Represents a tool use content block. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "tool_use") public class ToolUseBlock extends ContentBlock> implements ToolUseAssistantContent { @@ -98,14 +101,20 @@ public class ToolUseBlock extends ContentBlock> implements T } /** - * Sets the list of annotations. + * {@inheritDoc} * - * @param annotations The list of annotations + * Sets the list of annotations. */ + @Override public void setAnnotations(List annotations) { this.annotations = annotations; } + /** + * {@inheritDoc} + * + * Gets the content of the assistant. + */ @Override public Map getContentOfAssistant() { return Collections.emptyMap(); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java index 6486404f8..b7328b08e 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockDeltaEvent.java @@ -13,6 +13,9 @@ import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistant /** * Represents a content block delta event during streaming. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "content_block_delta") public class ContentBlockDeltaEvent extends StreamEvent { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java index eaf132934..758e59660 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStartEvent.java @@ -6,6 +6,9 @@ import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock; /** * Represents a content block start event during message streaming. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "content_block_start") public class ContentBlockStartEvent extends StreamEvent{ diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java index 9b4529b83..ed1241957 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/ContentBlockStopEvent.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Represents a content block stop event during message streaming. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "content_block_stop") public class ContentBlockStopEvent extends StreamEvent{ diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java index cdd89ba4a..2377e6662 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStartStreamEvent.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Represents a message start event during message streaming. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeName = "message_start") public class MessageStartStreamEvent extends StreamEvent{ diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java index 602ae4dfd..cbf32c27a 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/MessageStopStreamEvent.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Represents a message stop event during message streaming. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeName = "message_stop") public class MessageStopStreamEvent extends StreamEvent{ diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java index 1a4627dd5..b45c852ca 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/assistant/event/StreamEvent.java @@ -4,6 +4,9 @@ import com.alibaba.fastjson2.annotation.JSONType; /** * Base class for stream events during message streaming. + * + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "StreamEvent", seeAlso = {MessageStartStreamEvent.class, MessageStopStreamEvent.class, ContentBlockStartEvent.class, ContentBlockStopEvent.class, diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java index 6ddc66e41..64b5ffc78 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java @@ -5,6 +5,9 @@ import com.alibaba.qwen.code.cli.protocol.data.InitializeConfig; /** * Represents a control initialize request to the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class CLIControlInitializeRequest { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java index 69da95deb..2216de169 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java @@ -4,6 +4,9 @@ import com.alibaba.qwen.code.cli.protocol.data.Capabilities; /** * Represents a control initialize response from the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class CLIControlInitializeResponse { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java index 2b1ec9fc5..fb72114bc 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.protocol.message.control; /** * Represents a control interrupt request to the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class CLIControlInterruptRequest { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java index e03d86bad..2d488c96c 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java @@ -7,6 +7,9 @@ import com.alibaba.fastjson2.annotation.JSONField; /** * Represents a control permission request to the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class CLIControlPermissionRequest { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java index 2a1d22588..c5b23fe24 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java @@ -5,6 +5,9 @@ import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; /** * Represents a control permission response from the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class CLIControlPermissionResponse { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java index e12319cad..e888984be 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java @@ -10,6 +10,8 @@ import com.alibaba.qwen.code.cli.protocol.message.MessageBase; * Represents a control request to the CLI. * * @param The type of the request object + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "control_request") public class CLIControlRequest extends MessageBase { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java index f71c2156c..7e416786e 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlResponse.java @@ -8,6 +8,8 @@ import com.alibaba.qwen.code.cli.protocol.message.MessageBase; * Represents a control response from the CLI. * * @param The type of the response object + * @author skyfire + * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "control_response") public class CLIControlResponse extends MessageBase { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java index 8b1e5d8dc..2f2d4a751 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.protocol.message.control; /** * Represents a control request to set the model in the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class CLIControlSetModelRequest { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java index d5a0c2c87..6c3dc2c2c 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.protocol.message.control; /** * Represents a control response for setting the model in the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class CLIControlSetModelResponse { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java index 587fafdc5..7fb26851f 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.protocol.message.control; /** * Represents a control request to set the permission mode in the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class CLIControlSetPermissionModeRequest { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java index 2706bcb1a..95a6cb6ba 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java @@ -41,6 +41,9 @@ import org.slf4j.LoggerFactory; /** * Manages a session with the Qwen Code CLI, handling communication, sending prompts, and processing responses. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class Session { private static final Logger log = LoggerFactory.getLogger(Session.class); @@ -65,7 +68,7 @@ public class Session { * Constructs a new session with the specified transport. * * @param transport The transport layer to use for communication - * @throws SessionControlException if the transport is not available + * @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the transport is not available */ public Session(Transport transport) throws SessionControlException { if (transport == null || !transport.isAvailable()) { @@ -78,7 +81,7 @@ public class Session { /** * Starts the session by initializing communication with the CLI. * - * @throws SessionControlException if initialization fails + * @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if initialization fails */ public void start() throws SessionControlException { try { @@ -97,7 +100,7 @@ public class Session { /** * Closes the session and releases resources. * - * @throws SessionControlException if closing fails + * @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if closing fails */ public void close() throws SessionControlException { try { @@ -111,7 +114,7 @@ public class Session { * Interrupts the current operation in the CLI. * * @return An optional boolean indicating success of the interrupt operation - * @throws SessionControlException if the operation fails + * @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails */ public Optional interrupt() throws SessionControlException { checkAvailable(); @@ -123,7 +126,7 @@ public class Session { * * @param modelName The name of the model to use * @return An optional boolean indicating success of the operation - * @throws SessionControlException if the operation fails + * @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails */ public Optional setModel(String modelName) throws SessionControlException { checkAvailable(); @@ -137,7 +140,7 @@ public class Session { * * @param permissionMode The permission mode to use * @return An optional boolean indicating success of the operation - * @throws SessionControlException if the operation fails + * @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails */ public Optional setPermissionMode(PermissionMode permissionMode) throws SessionControlException { checkAvailable(); @@ -165,7 +168,7 @@ public class Session { /** * Continues the current session. * - * @throws SessionControlException if the operation fails + * @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails */ public void continueSession() throws SessionControlException { resumeSession(getSessionId()); @@ -175,7 +178,7 @@ public class Session { * Resumes a session with the specified ID. * * @param sessionId The ID of the session to resume - * @throws SessionControlException if the operation fails + * @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if the operation fails */ public void resumeSession(String sessionId) throws SessionControlException { if (StringUtils.isNotBlank(sessionId)) { @@ -189,8 +192,8 @@ public class Session { * * @param prompt The prompt to send to the CLI * @param sessionEventConsumers Consumers for handling different types of events - * @throws SessionSendPromptException if sending the prompt fails - * @throws SessionControlException if a control operation fails + * @throws com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException if sending the prompt fails + * @throws com.alibaba.qwen.code.cli.session.exception.SessionControlException if a control operation fails */ public void sendPrompt(String prompt, SessionEventConsumers sessionEventConsumers) throws SessionSendPromptException, SessionControlException { checkAvailable(); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java index 3338c4ac7..8fce1c4fa 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java @@ -10,6 +10,9 @@ import com.alibaba.qwen.code.cli.session.Session; /** * Interface for handling different types of assistant content during a session. + * + * @author skyfire + * @version $Id: 0.0.1 */ public interface AssistantContentConsumers { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java index f5c390a59..9a2b63df9 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java @@ -10,28 +10,37 @@ import com.alibaba.qwen.code.cli.session.Session; /** * Simple implementation of AssistantContentConsumers that provides empty implementations for all methods. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class AssistantContentSimpleConsumers implements AssistantContentConsumers { + /** {@inheritDoc} */ @Override public void onText(Session session, TextAssistantContent textAssistantContent) { } + /** {@inheritDoc} */ @Override public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { } + /** {@inheritDoc} */ @Override public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) { } + /** {@inheritDoc} */ @Override public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) { } + /** {@inheritDoc} */ @Override public void onOtherContent(Session session, AssistantContent other) { } + /** {@inheritDoc} */ @Override public void onUsage(Session session, AssistantUsage AssistantUsage) { } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java index da861ee8b..7159beaef 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java @@ -14,6 +14,9 @@ import com.alibaba.qwen.code.cli.utils.Timeout; /** * Interface for handling different types of events during a session. + * + * @author skyfire + * @version $Id: 0.0.1 */ public interface SessionEventConsumers { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java index 517cc022c..38ed31aa1 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java @@ -31,16 +31,22 @@ import org.slf4j.LoggerFactory; /** * Simple implementation of SessionEventConsumers that provides basic implementations for all methods. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class SessionEventSimpleConsumers implements SessionEventConsumers { + /** {@inheritDoc} */ @Override public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { } + /** {@inheritDoc} */ @Override public void onResultMessage(Session session, SDKResultMessage resultMessage) { } + /** {@inheritDoc} */ @Override public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) { List> contentBlocks = assistantMessage.getMessage().getContent(); @@ -54,6 +60,7 @@ public class SessionEventSimpleConsumers implements SessionEventConsumers { } } + /** {@inheritDoc} */ @Override public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) { StreamEvent event = partialAssistantMessage.getEvent(); @@ -67,6 +74,12 @@ public class SessionEventSimpleConsumers implements SessionEventConsumers { consumeAssistantContent(session, contentBlockDeltaEvent.getDelta()); } + /** + *

consumeAssistantContent.

+ * + * @param session a {@link com.alibaba.qwen.code.cli.session.Session} object. + * @param assistantContent a {@link com.alibaba.qwen.code.cli.protocol.data.AssistantContent} object. + */ protected void consumeAssistantContent(Session session, AssistantContent assistantContent) { if (assistantContent instanceof TextAssistantContent) { assistantContentConsumers.onText(session, (TextAssistantContent) assistantContent); @@ -81,23 +94,28 @@ public class SessionEventSimpleConsumers implements SessionEventConsumers { } } + /** {@inheritDoc} */ @Override public void onUserMessage(Session session, SDKUserMessage userMessage) { } + /** {@inheritDoc} */ @Override public void onOtherMessage(Session session, String message) { } + /** {@inheritDoc} */ @Override public void onControlResponse(Session session, CLIControlResponse cliControlResponse) { } + /** {@inheritDoc} */ @Override public CLIControlResponse onControlRequest(Session session, CLIControlRequest cliControlRequest) { return new CLIControlResponse<>(); } + /** {@inheritDoc} */ @Override public Behavior onPermissionRequest(Session session, CLIControlRequest permissionRequest) { if (Operation.deny.equals(this.defaultPermissionOperation)) { @@ -107,46 +125,55 @@ public class SessionEventSimpleConsumers implements SessionEventConsumers { } } + /** {@inheritDoc} */ @Override public Timeout onSystemMessageTimeout(Session session) { return defaultEventTimeout; } + /** {@inheritDoc} */ @Override public Timeout onResultMessageTimeout(Session session) { return defaultEventTimeout; } + /** {@inheritDoc} */ @Override public Timeout onAssistantMessageTimeout(Session session) { return defaultEventTimeout; } + /** {@inheritDoc} */ @Override public Timeout onPartialAssistantMessageTimeout(Session session) { return defaultEventTimeout; } + /** {@inheritDoc} */ @Override public Timeout onUserMessageTimeout(Session session) { return defaultEventTimeout; } + /** {@inheritDoc} */ @Override public Timeout onOtherMessageTimeout(Session session) { return defaultEventTimeout; } + /** {@inheritDoc} */ @Override public Timeout onControlResponseTimeout(Session session) { return defaultEventTimeout; } + /** {@inheritDoc} */ @Override public Timeout onControlRequestTimeout(Session session) { return defaultEventTimeout; } + /** {@inheritDoc} */ @Override public Timeout onPermissionRequestTimeout(Session session) { return defaultEventTimeout; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java index 3fa30bd55..ad629e895 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionControlException.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.session.exception; /** * Exception thrown when a session control operation fails. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class SessionControlException extends Exception { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java index 460c5f560..6f2c87f0c 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/exception/SessionSendPromptException.java @@ -2,6 +2,9 @@ package com.alibaba.qwen.code.cli.session.exception; /** * Exception thrown when sending a prompt in a session fails. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class SessionSendPromptException extends Exception { /** diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java index 1bb46020e..0171821e1 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/Transport.java @@ -7,6 +7,9 @@ import java.util.function.Function; /** * Defines the contract for communication with the Qwen Code CLI. + * + * @author skyfire + * @version $Id: 0.0.1 */ public interface Transport { /** @@ -26,14 +29,14 @@ public interface Transport { /** * Starts the transport. * - * @throws IOException if starting fails + * @throws java.io.IOException if starting fails */ void start() throws IOException; /** * Closes the transport and releases resources. * - * @throws IOException if closing fails + * @throws java.io.IOException if closing fails */ void close() throws IOException; @@ -49,10 +52,10 @@ public interface Transport { * * @param message The message to send * @return The response message - * @throws IOException if an I/O error occurs - * @throws ExecutionException if an execution error occurs - * @throws InterruptedException if the operation is interrupted - * @throws TimeoutException if the operation times out + * @throws java.io.IOException if an I/O error occurs + * @throws java.util.concurrent.ExecutionException if an execution error occurs + * @throws java.lang.InterruptedException if the operation is interrupted + * @throws java.util.concurrent.TimeoutException if the operation times out */ String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException; @@ -61,7 +64,7 @@ public interface Transport { * * @param message The message to send * @param callBackFunction A function to process each line of the response - * @throws IOException if an I/O error occurs + * @throws java.io.IOException if an I/O error occurs */ void inputWaitForMultiLine(String message, Function callBackFunction) throws IOException; @@ -69,7 +72,7 @@ public interface Transport { * Sends a message without waiting for a response. * * @param message The message to send - * @throws IOException if an I/O error occurs + * @throws java.io.IOException if an I/O error occurs */ void inputNoWaitResponse(String message) throws IOException; } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java index a73f1c13d..5f72e1c0b 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java @@ -8,6 +8,9 @@ import com.alibaba.qwen.code.cli.utils.Timeout; /** * Configuration options for the transport layer. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class TransportOptions implements Cloneable { /** @@ -395,6 +398,7 @@ public class TransportOptions implements Cloneable { return this; } + /** {@inheritDoc} */ @Override public TransportOptions clone() { try { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java index fb590e9c0..ee53c21f7 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransport.java @@ -24,6 +24,9 @@ import java.util.function.Function; /** * Implementation of the Transport interface that communicates with the Qwen CLI via a process. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class ProcessTransport implements Transport { private static final Logger log = LoggerFactory.getLogger(ProcessTransport.class); @@ -42,7 +45,7 @@ public class ProcessTransport implements Transport { /** * Constructs a new ProcessTransport with default options. * - * @throws IOException if starting the process fails + * @throws java.io.IOException if starting the process fails */ public ProcessTransport() throws IOException { this(new TransportOptions()); @@ -52,7 +55,7 @@ public class ProcessTransport implements Transport { * Constructs a new ProcessTransport with the specified options. * * @param transportOptions The transport options to use - * @throws IOException if starting the process fails + * @throws java.io.IOException if starting the process fails */ public ProcessTransport(TransportOptions transportOptions) throws IOException { this(transportOptions, (line) -> log.error("process error: {}", line)); @@ -63,7 +66,7 @@ public class ProcessTransport implements Transport { * * @param transportOptions The transport options to use * @param errorHandler The error handler to use - * @throws IOException if starting the process fails + * @throws java.io.IOException if starting the process fails */ public ProcessTransport(TransportOptions transportOptions, Consumer errorHandler) throws IOException { this.transportOptions = transportOptions; @@ -71,16 +74,19 @@ public class ProcessTransport implements Transport { start(); } + /** {@inheritDoc} */ @Override public TransportOptions getTransportOptions() { return transportOptions; } + /** {@inheritDoc} */ @Override public boolean isReading() { return reading.get(); } + /** {@inheritDoc} */ @Override public void start() throws IOException { TransportOptionsAdapter transportOptionsAdapter = new TransportOptionsAdapter(transportOptions); @@ -104,6 +110,7 @@ public class ProcessTransport implements Transport { startErrorReading(); } + /** {@inheritDoc} */ @Override public void close() throws IOException { if (processInput != null) { @@ -120,11 +127,13 @@ public class ProcessTransport implements Transport { } } + /** {@inheritDoc} */ @Override public boolean isAvailable() { return process != null && process.isAlive(); } + /** {@inheritDoc} */ @Override public String inputWaitForOneLine(String message) throws IOException, ExecutionException, InterruptedException, TimeoutException { return inputWaitForOneLine(message, turnTimeout); @@ -150,6 +159,7 @@ public class ProcessTransport implements Transport { } } + /** {@inheritDoc} */ @Override public void inputWaitForMultiLine(String message, Function callBackFunction) throws IOException { inputWaitForMultiLine(message, callBackFunction, turnTimeout); @@ -161,6 +171,7 @@ public class ProcessTransport implements Transport { MyConcurrentUtils.runAndWait(() -> iterateOutput(callBackFunction), timeOut); } + /** {@inheritDoc} */ @Override public void inputNoWaitResponse(String message) throws IOException { log.debug("input message to process: {}", message); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java index d50892701..34c7585d5 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/MyConcurrentUtils.java @@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory; /** * Utility class for concurrent operations. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class MyConcurrentUtils { private static final Logger log = LoggerFactory.getLogger(MyConcurrentUtils.class); @@ -50,9 +53,9 @@ public class MyConcurrentUtils { * @param timeOut The timeout for the operation * @param The type of the result * @return The result of the task - * @throws ExecutionException if an execution error occurs - * @throws InterruptedException if the operation is interrupted - * @throws TimeoutException if the operation times out + * @throws java.util.concurrent.ExecutionException if an execution error occurs + * @throws java.lang.InterruptedException if the operation is interrupted + * @throws java.util.concurrent.TimeoutException if the operation times out */ public static T runAndWait(Supplier supplier, Timeout timeOut) throws ExecutionException, InterruptedException, TimeoutException { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java index 671bdde74..8213cdef8 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java @@ -11,6 +11,9 @@ import java.util.function.Supplier; /** * Configuration for the thread pool used by the SDK. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class ThreadPoolConfig { private static final ThreadPoolExecutor defaultExecutor = new ThreadPoolExecutor( diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java index 76fb29ab8..e221cddbb 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java @@ -6,6 +6,9 @@ import org.apache.commons.lang3.Validate; /** * Represents a timeout value with a time unit. + * + * @author skyfire + * @version $Id: 0.0.1 */ public class Timeout { /** From 51b08f700c786c4ae86e78954dc827c964f2c1f9 Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 5 Jan 2026 17:44:07 +0800 Subject: [PATCH 41/65] for examples --- packages/sdk-java/QWEN.md | 268 +++++- packages/sdk-java/README.md | 856 ++++-------------- packages/sdk-java/pom.xml | 41 +- .../alibaba/qwen/code/cli/QwenCodeCli.java | 12 +- .../cli/protocol/data/behavior/Behavior.java | 4 + .../control/CLIControlInterruptRequest.java | 32 - .../message/control/CLIControlRequest.java | 5 +- .../control/CLIControlSetModelRequest.java | 54 -- .../CLIControlSetPermissionModeRequest.java | 55 -- .../CLIControlInitializeRequest.java | 32 +- .../CLIControlInitializeResponse.java | 33 +- .../payload/CLIControlInterruptRequest.java | 17 + .../CLIControlPermissionRequest.java | 32 +- .../CLIControlPermissionResponse.java | 32 +- .../payload/CLIControlSetModelRequest.java | 40 + .../CLIControlSetModelResponse.java | 2 +- .../CLIControlSetPermissionModeRequest.java | 40 + .../payload/ControlRequestPayload.java | 26 + .../payload/ControlResponsePayload.java | 26 + .../qwen/code/cli/session/Session.java | 134 +-- .../event/AssistantContentConsumers.java | 65 -- .../AssistantContentSimpleConsumers.java | 47 - .../event/SessionEventSimpleConsumers.java | 266 ------ .../consumers/AssistantContentConsumers.java | 159 ++++ .../AssistantContentSimpleConsumers.java | 176 ++++ .../SessionEventConsumers.java | 49 +- .../SessionEventSimpleConsumers.java | 339 +++++++ .../qwen/code/cli/utils/ThreadPoolConfig.java | 2 +- .../alibaba/qwen/code/cli/utils/Timeout.java | 6 + .../code/cli/example/QuickStartExample.java | 109 +++ .../qwen/code/cli/example/SessionExample.java | 256 ++++++ .../ThreadPoolConfigurationExample.java | 50 + .../qwen/code/cli/session/SessionTest.java | 34 +- .../process/ProcessTransportTest.java | 4 +- 34 files changed, 1808 insertions(+), 1495 deletions(-) delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java rename packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/{ => payload}/CLIControlInitializeRequest.java (63%) rename packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/{ => payload}/CLIControlInitializeResponse.java (55%) create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInterruptRequest.java rename packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/{ => payload}/CLIControlPermissionRequest.java (90%) rename packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/{ => payload}/CLIControlPermissionResponse.java (60%) create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetModelRequest.java rename packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/{ => payload}/CLIControlSetModelResponse.java (93%) create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetPermissionModeRequest.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/ControlRequestPayload.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/ControlResponsePayload.java delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java delete mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentConsumers.java create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentSimpleConsumers.java rename packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/{ => consumers}/SessionEventConsumers.java (71%) create mode 100644 packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/SessionEventSimpleConsumers.java create mode 100644 packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/QuickStartExample.java create mode 100644 packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/SessionExample.java create mode 100644 packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/ThreadPoolConfigurationExample.java diff --git a/packages/sdk-java/QWEN.md b/packages/sdk-java/QWEN.md index 0ebb55c7d..fab09cf5c 100644 --- a/packages/sdk-java/QWEN.md +++ b/packages/sdk-java/QWEN.md @@ -4,23 +4,30 @@ 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: +**Context Information:** + +- Current Date: Monday 5 January 2026 +- Operating System: darwin +- Working Directory: /Users/weigeng/repos/qwen-code/packages/sdk-java + +## Project Details - **Group ID**: com.alibaba -- **Artifact ID**: qwencode-sdk-java -- **Version**: 0.0.1 +- **Artifact ID**: qwencode-sdk (as per pom.xml) +- **Version**: 0.0.1-SNAPSHOT - **Packaging**: JAR - **Java Version**: 1.8+ (source and target) +- **License**: Apache-2.0 ## Architecture The SDK follows a layered architecture: -- **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 +- **API Layer**: Provides the main entry points through `QwenCodeCli` class with simple static methods for basic usage +- **Session Layer**: Manages communication sessions with the Qwen Code CLI through the `Session` class +- **Transport Layer**: Handles the communication mechanism between the SDK and CLI process (currently using process transport via `ProcessTransport`) +- **Protocol Layer**: Defines data structures for communication based on the CLI protocol +- **Utils**: Common utilities for concurrent execution, timeout handling, and error management ## Key Components @@ -30,6 +37,9 @@ The SDK follows a layered architecture: - `Session`: Manages communication sessions with the CLI - `Transport`: Abstracts the communication mechanism (currently using process transport) - `ProcessTransport`: Implementation that communicates via process execution +- `TransportOptions`: Configuration class for transport layer settings +- `SessionEventSimpleConsumers`: High-level event handler for processing responses +- `AssistantContentSimpleConsumers`: Handles different types of content within assistant messages ### Dependencies @@ -62,6 +72,9 @@ mvn install # Run checkstyle verification mvn checkstyle:check + +# Generate Javadoc +mvn javadoc:javadoc ``` ### Testing @@ -76,6 +89,9 @@ The project uses Checkstyle for code formatting and style enforcement. The confi - Naming conventions - Import ordering - Code structure +- Line endings (LF only) +- No trailing whitespace +- 8-space indentation for line wrapping ## Development Conventions @@ -105,10 +121,13 @@ The project uses Checkstyle for code formatting and style enforcement. The confi ### QwenCodeCli Class -The main class provides two primary methods: +The main class provides several primary methods: - `simpleQuery(String prompt)`: Synchronous method that returns a list of responses -- `simpleQuery(String prompt, Consumer messageConsumer)`: Asynchronous method that streams responses to a consumer +- `simpleQuery(String prompt, TransportOptions transportOptions)`: Synchronous method with custom transport options +- `simpleQuery(String prompt, TransportOptions transportOptions, AssistantContentConsumers assistantContentConsumers)`: Advanced method with custom content consumers +- `newSession()`: Creates a new session with default options +- `newSession(TransportOptions transportOptions)`: Creates a new session with custom options ### Permission Modes @@ -119,24 +138,175 @@ The SDK supports different permission modes for controlling tool execution: - **`auto-edit`**: Auto-approve edit tools (edit, write_file) while other tools require confirmation. - **`yolo`**: All tools execute automatically without confirmation. -## Usage Example +### Transport Options -```java -import com.alibaba.qwen.code.cli.QwenCodeCli; -import java.util.List; +The `TransportOptions` class allows configuration of how the SDK communicates with the Qwen Code CLI: -public class Example { - public static void main(String[] args) { - List result = QwenCodeCli.simpleQuery("hello world"); - result.forEach(System.out::println); - } -} -``` +- `pathToQwenExecutable`: Path to the Qwen Code CLI executable +- `cwd`: Working directory for the CLI process +- `model`: AI model to use for the session +- `permissionMode`: Permission mode that controls tool execution +- `env`: Environment variables to pass to the CLI process +- `maxSessionTurns`: Limits the number of conversation turns in a session +- `coreTools`: List of core tools that should be available to the AI +- `excludeTools`: List of tools to exclude from being available to the AI +- `allowedTools`: List of tools that are pre-approved for use without additional confirmation +- `authType`: Authentication type to use for the session +- `includePartialMessages`: Enables receiving partial messages during streaming responses +- `skillsEnable`: Enables or disables skills functionality for the session +- `turnTimeout`: Timeout for a complete turn of conversation +- `messageTimeout`: Timeout for individual messages within a turn +- `resumeSessionId`: ID of a previous session to resume +- `otherOptions`: Additional command-line options to pass to the CLI + +### Session Control Features + +- **Session creation**: Use `QwenCodeCli.newSession()` to create a new session with custom options +- **Session management**: The `Session` class provides methods to send prompts, handle responses, and manage session state +- **Session cleanup**: Always close sessions using `session.close()` to properly terminate the CLI process +- **Session resumption**: Use `setResumeSessionId()` in `TransportOptions` to resume a previous session +- **Session interruption**: Use `session.interrupt()` to interrupt a currently running prompt +- **Dynamic model switching**: Use `session.setModel()` to change the model during a session +- **Dynamic permission mode switching**: Use `session.setPermissionMode()` to change the permission mode during a session + +### Thread Pool Configuration + +The SDK uses a thread pool for managing concurrent operations with the following default configuration: + +- **Core Pool Size**: 30 threads +- **Maximum Pool Size**: 100 threads +- **Keep-Alive Time**: 60 seconds +- **Queue Capacity**: 300 tasks (using LinkedBlockingQueue) +- **Thread Naming**: "qwen_code_cli-pool-{number}" +- **Daemon Threads**: false +- **Rejected Execution Handler**: CallerRunsPolicy + +### Session Event Consumers and Assistant Content Consumers + +The SDK provides two key interfaces for handling events and content from the CLI: + +#### SessionEventConsumers Interface + +The `SessionEventConsumers` interface provides callbacks for different types of messages during a session: + +- `onSystemMessage`: Handles system messages from the CLI (receives Session and SDKSystemMessage) +- `onResultMessage`: Handles result messages from the CLI (receives Session and SDKResultMessage) +- `onAssistantMessage`: Handles assistant messages (AI responses) (receives Session and SDKAssistantMessage) +- `onPartialAssistantMessage`: Handles partial assistant messages during streaming (receives Session and SDKPartialAssistantMessage) +- `onUserMessage`: Handles user messages (receives Session and SDKUserMessage) +- `onOtherMessage`: Handles other types of messages (receives Session and String message) +- `onControlResponse`: Handles control responses (receives Session and CLIControlResponse) +- `onControlRequest`: Handles control requests (receives Session and CLIControlRequest, returns CLIControlResponse) +- `onPermissionRequest`: Handles permission requests (receives Session and CLIControlRequest, returns Behavior) + +#### AssistantContentConsumers Interface + +The `AssistantContentConsumers` interface handles different types of content within assistant messages: + +- `onText`: Handles text content (receives Session and TextAssistantContent) +- `onThinking`: Handles thinking content (receives Session and ThingkingAssistantContent) +- `onToolUse`: Handles tool use content (receives Session and ToolUseAssistantContent) +- `onToolResult`: Handles tool result content (receives Session and ToolResultAssistantContent) +- `onOtherContent`: Handles other content types (receives Session and AssistantContent) +- `onUsage`: Handles usage information (receives Session and AssistantUsage) +- `onPermissionRequest`: Handles permission requests (receives Session and CLIControlPermissionRequest, returns Behavior) +- `onOtherControlRequest`: Handles other control requests (receives Session and ControlRequestPayload, returns ControlResponsePayload) + +#### Relationship Between the Interfaces + +**Important Note on Event Hierarchy:** + +- `SessionEventConsumers` is the **high-level** event processor that handles different message types (system, assistant, user, etc.) +- `AssistantContentConsumers` is the **low-level** content processor that handles different types of content within assistant messages (text, tools, thinking, etc.) + +**Processor Relationship:** + +- `SessionEventConsumers` → `AssistantContentConsumers` (SessionEventConsumers uses AssistantContentConsumers to process content within assistant messages) + +**Event Derivation Relationships:** + +- `onAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent`, `onUsage` +- `onPartialAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent` +- `onControlRequest` → `onPermissionRequest`, `onOtherControlRequest` + +**Event Timeout Relationships:** + +Each event handler method has a corresponding timeout method that allows customizing the timeout behavior for that specific event: + +- `onSystemMessage` ↔ `onSystemMessageTimeout` +- `onResultMessage` ↔ `onResultMessageTimeout` +- `onAssistantMessage` ↔ `onAssistantMessageTimeout` +- `onPartialAssistantMessage` ↔ `onPartialAssistantMessageTimeout` +- `onUserMessage` ↔ `onUserMessageTimeout` +- `onOtherMessage` ↔ `onOtherMessageTimeout` +- `onControlResponse` ↔ `onControlResponseTimeout` +- `onControlRequest` ↔ `onControlRequestTimeout` + +For AssistantContentConsumers timeout methods: + +- `onText` ↔ `onTextTimeout` +- `onThinking` ↔ `onThinkingTimeout` +- `onToolUse` ↔ `onToolUseTimeout` +- `onToolResult` ↔ `onToolResultTimeout` +- `onOtherContent` ↔ `onOtherContentTimeout` +- `onPermissionRequest` ↔ `onPermissionRequestTimeout` +- `onOtherControlRequest` ↔ `onOtherControlRequestTimeout` + +**Default Timeout Values:** + +- `SessionEventSimpleConsumers` default timeout: 180 seconds (Timeout.TIMEOUT_180_SECONDS) +- `AssistantContentSimpleConsumers` default timeout: 60 seconds (Timeout.TIMEOUT_60_SECONDS) + +**Timeout Hierarchy Requirements:** + +For proper operation, the following timeout relationships should be maintained: + +- `onAssistantMessageTimeout` return value should be greater than `onTextTimeout`, `onThinkingTimeout`, `onToolUseTimeout`, `onToolResultTimeout`, and `onOtherContentTimeout` return values +- `onControlRequestTimeout` return value should be greater than `onPermissionRequestTimeout` and `onOtherControlRequestTimeout` return values + +#### Relationship Between the Interfaces + +- `AssistantContentSimpleConsumers` is the default implementation of `AssistantContentConsumers` +- `SessionEventSimpleConsumers` is the concrete implementation that combines both interfaces and depends on an `AssistantContentConsumers` instance to handle content within assistant messages +- The timeout methods in `SessionEventConsumers` now include the message object as a parameter (e.g., `onSystemMessageTimeout(Session session, SDKSystemMessage systemMessage)`) + +Event processing is subject to the timeout settings configured in `TransportOptions` and `SessionEventConsumers`. For detailed timeout configuration options, see the "Timeout" section above. + +## Usage Examples + +The SDK includes several example files in `src/test/java/com/alibaba/qwen/code/cli/example/` that demonstrate different aspects of the API: + +### Basic Usage + +- `QuickStartExample.java`: Demonstrates simple query usage, transport options configuration, and streaming content handling + +### Session Control + +- `SessionExample.java`: Shows session control features including permission mode changes, model switching, interruption, and event handling + +### Configuration + +- `ThreadPoolConfigurationExample.java`: Shows how to configure the thread pool used by the SDK + +## Error Handling + +The SDK provides specific exception types for different error scenarios: + +- `SessionControlException`: Thrown when there's an issue with session control (creation, initialization, etc.) +- `SessionSendPromptException`: Thrown when there's an issue sending a prompt or receiving a response +- `SessionClosedException`: Thrown when attempting to use a closed session ## Project Structure ``` src/ +├── example/ +│ └── java/ +│ └── com/ +│ └── alibaba/ +│ └── qwen/ +│ └── code/ +│ └── example/ ├── main/ │ └── java/ │ └── com/ @@ -150,13 +320,20 @@ src/ │ ├── transport/ │ └── utils/ └── test/ - └── java/ - └── com/ - └── alibaba/ - └── qwen/ - └── code/ - └── cli/ - └── QwenCodeCliTest.java + ├── java/ + │ └── com/ + │ └── alibaba/ + │ └── qwen/ + │ └── code/ + │ └── cli/ + │ ├── QwenCodeCliTest.java + │ ├── session/ + │ │ └── SessionTest.java + │ └── transport/ + │ ├── PermissionModeTest.java + │ └── process/ + │ └── ProcessTransportTest.java + └── temp/ ``` ## Configuration Files @@ -165,6 +342,37 @@ src/ - `checkstyle.xml`: Code style and formatting rules - `.editorconfig`: Editor configuration settings -## License +## FAQ / Troubleshooting -Apache-2.0 - see [LICENSE](./LICENSE) for details. +### Q: Do I need to install the Qwen CLI separately? + +A: No, from v0.1.1, the CLI is bundled with the SDK, so no standalone CLI installation is needed. + +### Q: What Java versions are supported? + +A: The SDK requires Java 1.8 or higher. + +### Q: How do I handle long-running requests? + +A: The SDK includes timeout utilities. You can configure timeouts using the `Timeout` class in `TransportOptions`. + +### Q: Why are some tools not executing? + +A: This is likely due to permission modes. Check your permission mode settings and consider using `allowedTools` to pre-approve certain tools. + +### Q: How do I resume a previous session? + +A: Use the `setResumeSessionId()` method in `TransportOptions` to resume a previous session. + +### Q: Can I customize the environment for the CLI process? + +A: Yes, use the `setEnv()` method in `TransportOptions` to pass environment variables to the CLI process. + +### Q: What happens if the CLI process crashes? + +A: The SDK will throw appropriate exceptions. Make sure to handle `SessionControlException` and implement retry logic if needed. + +## Maintainers + +- **Developer**: skyfire (gengwei.gw(at)alibaba-inc.com) +- **Organization**: Alibaba Group diff --git a/packages/sdk-java/README.md b/packages/sdk-java/README.md index 5794a9a16..e9987d946 100644 --- a/packages/sdk-java/README.md +++ b/packages/sdk-java/README.md @@ -1,8 +1,19 @@ # 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. +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. -Feel free to submit a feature request/issue/PR. +## Requirements + +- Java >= 1.8 +- Maven >= 3.6.0 (for building from source) + +### Dependencies + +- **Logging**: ch.qos.logback:logback-classic +- **Utilities**: org.apache.commons:commons-lang3 +- **JSON Processing**: com.alibaba.fastjson2:fastjson2 +- **Testing**: JUnit 5 (org.junit.jupiter:junit-jupiter) +- ## Installation @@ -11,91 +22,111 @@ Add the following dependency to your Maven `pom.xml`: ```xml com.alibaba - qwencode-sdk-java - 0.0.1 + qwencode-sdk + {$version} ``` Or if using Gradle, add to your `build.gradle`: ```gradle -implementation 'com.alibaba:qwencode-sdk-java:0.0.1' +implementation 'com.alibaba:qwencode-sdk:{$version}' ``` -## Requirements +## Building and Running -- 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. +### Build Commands + +```bash +# Compile the project +mvn compile + +# Run tests +mvn test + +# Package the JAR +mvn package + +# Install to local repository +mvn install +``` ## Quick Start The simplest way to use the SDK is through the `QwenCodeCli.simpleQuery()` method: ```java -import com.alibaba.qwen.code.cli.QwenCodeCli; -import java.util.List; - -public class Example { - public static void main(String[] args) { - List result = QwenCodeCli.simpleQuery("hello world"); - result.forEach(System.out::println); - } +public static void runSimpleExample() { + List result = QwenCodeCli.simpleQuery("hello world"); + result.forEach(logger::info); } ``` -For more advanced usage with streaming responses: +For more advanced usage with custom transport options: ```java -import com.alibaba.qwen.code.cli.QwenCodeCli; -import java.util.function.Consumer; +public static void runTransportOptionsExample() { + TransportOptions options = new TransportOptions() + .setModel("qwen3-coder-flash") + .setPermissionMode(PermissionMode.AUTO_EDIT) + .setCwd("./") + .setEnv(new HashMap() {{put("CUSTOM_VAR", "value");}}) + .setIncludePartialMessages(true) + .setTurnTimeout(new Timeout(120L, TimeUnit.SECONDS)) + .setMessageTimeout(new Timeout(90L, TimeUnit.SECONDS)) + .setAllowedTools(Arrays.asList("read_file", "write_file", "list_directory")); -public class StreamingExample { - public static void main(String[] args) { - QwenCodeCli.simpleQuery("hello world", (String message) -> { - System.out.println("Received: " + message); - }); - } + List result = QwenCodeCli.simpleQuery("who are you, what are your capabilities?", options); + result.forEach(logger::info); } ``` -For session-based usage with custom event handling: +For streaming content handling with custom content consumers: ```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 static void runStreamingExample() { + QwenCodeCli.simpleQuery("who are you, what are your capabilities?", + new TransportOptions().setMessageTimeout(new Timeout(10L, TimeUnit.SECONDS)), new AssistantContentSimpleConsumers() { -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); + public void onText(Session session, TextAssistantContent textAssistantContent) { + logger.info("Text content received: {}", textAssistantContent.getText()); } - }.setDefaultEventTimeout(new Timeout(60L, TimeUnit.SECONDS)); - session.sendPrompt("hello world", eventConsumers); - } catch (Exception e) { - e.printStackTrace(); - } - } + @Override + public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { + logger.info("Thinking content received: {}", thingkingAssistantContent.getThinking()); + } + + @Override + public void onToolUse(Session session, ToolUseAssistantContent toolUseContent) { + logger.info("Tool use content received: {} with arguments: {}", + toolUseContent, toolUseContent.getInput()); + } + + @Override + public void onToolResult(Session session, ToolResultAssistantContent toolResultContent) { + logger.info("Tool result content received: {}", toolResultContent.getContent()); + } + + @Override + public void onOtherContent(Session session, AssistantContent other) { + logger.info("Other content received: {}", other); + } + + @Override + public void onUsage(Session session, AssistantUsage assistantUsage) { + logger.info("Usage information received: Input tokens: {}, Output tokens: {}", + assistantUsage.getUsage().getInputTokens(), assistantUsage.getUsage().getOutputTokens()); + } + }.setDefaultPermissionOperation(Operation.allow)); + logger.info("Streaming example completed."); } ``` ## Architecture -The Qwen Code Java SDK follows a layered architecture that abstracts the communication with the Qwen Code CLI: - -### Layered Architecture +The SDK follows a layered architecture: - **API Layer**: Provides the main entry points through `QwenCodeCli` class with simple static methods for basic usage - **Session Layer**: Manages communication sessions with the Qwen Code CLI through the `Session` class @@ -103,169 +134,7 @@ The Qwen Code Java SDK follows a layered architecture that abstracts the communi - **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, returns Behavior) -- `onAssistantMessageIncludePartial`: Handles assistant messages including partial content (specific to SessionEventSimpleConsumers, called by both onAssistantMessage and onPartialAssistantMessage) (receives Session, List, 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.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 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 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); - } -} -``` +## Key Features ### Permission Modes @@ -276,25 +145,111 @@ The SDK supports different permission modes for controlling tool execution: - **`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: +### Session Event Consumers and Assistant Content Consumers -```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; +The SDK provides two key interfaces for handling events and content from the CLI: -public class PermissionModeExample { - public static void main(String[] args) { - Session session = QwenCodeCli.newSession(new TransportOptions().setPermissionMode(PermissionMode.YOLO)); - session.setPermissionMode(PermissionMode.PLAN); - } -} -``` +#### SessionEventConsumers Interface -### Session Control +The `SessionEventConsumers` interface provides callbacks for different types of messages during a session: -The SDK provides fine-grained control over session lifecycle and behavior: +- `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, returns Behavior) + +#### AssistantContentConsumers Interface + +The `AssistantContentConsumers` interface handles different types of content within assistant messages: + +- `onText`: Handles text content (receives Session and TextAssistantContent) +- `onThinking`: Handles thinking content (receives Session and ThingkingAssistantContent) +- `onToolUse`: Handles tool use content (receives Session and ToolUseAssistantContent) +- `onToolResult`: Handles tool result content (receives Session and ToolResultAssistantContent) +- `onOtherContent`: Handles other content types (receives Session and AssistantContent) +- `onUsage`: Handles usage information (receives Session and AssistantUsage) +- `onPermissionRequest`: Handles permission requests (receives Session and CLIControlPermissionRequest, returns Behavior) +- `onOtherControlRequest`: Handles other control requests (receives Session and ControlRequestPayload, returns ControlResponsePayload) + +#### Relationship Between the Interfaces + +**Important Note on Event Hierarchy:** + +- `SessionEventConsumers` is the **high-level** event processor that handles different message types (system, assistant, user, etc.) +- `AssistantContentConsumers` is the **low-level** content processor that handles different types of content within assistant messages (text, tools, thinking, etc.) + +**Processor Relationship:** + +- `SessionEventConsumers` → `AssistantContentConsumers` (SessionEventConsumers uses AssistantContentConsumers to process content within assistant messages) + +**Event Derivation Relationships:** + +- `onAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent`, `onUsage` +- `onPartialAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent` +- `onControlRequest` → `onPermissionRequest`, `onOtherControlRequest` + +**Event Timeout Relationships:** + +Each event handler method has a corresponding timeout method that allows customizing the timeout behavior for that specific event: + +- `onSystemMessage` ↔ `onSystemMessageTimeout` +- `onResultMessage` ↔ `onResultMessageTimeout` +- `onAssistantMessage` ↔ `onAssistantMessageTimeout` +- `onPartialAssistantMessage` ↔ `onPartialAssistantMessageTimeout` +- `onUserMessage` ↔ `onUserMessageTimeout` +- `onOtherMessage` ↔ `onOtherMessageTimeout` +- `onControlResponse` ↔ `onControlResponseTimeout` +- `onControlRequest` ↔ `onControlRequestTimeout` + +For AssistantContentConsumers timeout methods: + +- `onText` ↔ `onTextTimeout` +- `onThinking` ↔ `onThinkingTimeout` +- `onToolUse` ↔ `onToolUseTimeout` +- `onToolResult` ↔ `onToolResultTimeout` +- `onOtherContent` ↔ `onOtherContentTimeout` +- `onPermissionRequest` ↔ `onPermissionRequestTimeout` +- `onOtherControlRequest` ↔ `onOtherControlRequestTimeout` + +**Default Timeout Values:** + +- `SessionEventSimpleConsumers` default timeout: 180 seconds (Timeout.TIMEOUT_180_SECONDS) +- `AssistantContentSimpleConsumers` default timeout: 60 seconds (Timeout.TIMEOUT_60_SECONDS) + +**Timeout Hierarchy Requirements:** + +For proper operation, the following timeout relationships should be maintained: + +- `onAssistantMessageTimeout` return value should be greater than `onTextTimeout`, `onThinkingTimeout`, `onToolUseTimeout`, `onToolResultTimeout`, and `onOtherContentTimeout` return values +- `onControlRequestTimeout` return value should be greater than `onPermissionRequestTimeout` and `onOtherControlRequestTimeout` return values + +### Transport Options + +The `TransportOptions` class allows configuration of how the SDK communicates with the Qwen Code CLI: + +- `pathToQwenExecutable`: Path to the Qwen Code CLI executable +- `cwd`: Working directory for the CLI process +- `model`: AI model to use for the session +- `permissionMode`: Permission mode that controls tool execution +- `env`: Environment variables to pass to the CLI process +- `maxSessionTurns`: Limits the number of conversation turns in a session +- `coreTools`: List of core tools that should be available to the AI +- `excludeTools`: List of tools to exclude from being available to the AI +- `allowedTools`: List of tools that are pre-approved for use without additional confirmation +- `authType`: Authentication type to use for the session +- `includePartialMessages`: Enables receiving partial messages during streaming responses +- `skillsEnable`: Enables or disables skills functionality for the session +- `turnTimeout`: Timeout for a complete turn of conversation +- `messageTimeout`: Timeout for individual messages within a turn +- `resumeSessionId`: ID of a previous session to resume +- `otherOptions`: Additional command-line options to pass to the CLI + +### Session Control Features - **Session creation**: Use `QwenCodeCli.newSession()` to create a new session with custom options - **Session management**: The `Session` class provides methods to send prompts, handle responses, and manage session state @@ -304,415 +259,19 @@ The SDK provides fine-grained control over session lifecycle and behavior: - **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 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 interrupt() throws SessionControlException` -- **Purpose**: Interrupts the current prompt processing without closing the session -- **Return value**: An `Optional` 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 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 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` 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 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 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 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` 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 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 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: +The SDK uses a thread pool for managing concurrent operations with the following default configuration: -- **Core Pool Size**: 10 threads -- **Maximum Pool Size**: 30 threads +- **Core Pool Size**: 30 threads +- **Maximum Pool Size**: 100 threads - **Keep-Alive Time**: 60 seconds - **Queue Capacity**: 300 tasks (using LinkedBlockingQueue) - **Thread Naming**: "qwen_code_cli-pool-{number}" - **Daemon Threads**: false -- **Rejected Execution Handler**: CallerRunsPolicy (executes the task on the calling thread when the pool is full) +- **Rejected Execution Handler**: CallerRunsPolicy -The thread pool can be customized in two ways: - -1. **Using a custom supplier**: Provide a custom `Supplier` 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() { - @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` - - 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` - - 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` - - Example: `new TransportOptions().setExcludeTools(List.of("shell"))` - -- **`allowedTools`**: Specifies a list of tools that are pre-approved for use without additional confirmation. - - Type: `List` - - 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` - - 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 result = session.sendPrompt("Analyze the current project", new SessionEventSimpleConsumers()); - result.forEach(System.out::println); - } - } -} -``` - -### Error Handling +## Error Handling The SDK provides specific exception types for different error scenarios: @@ -720,37 +279,6 @@ The SDK provides specific exception types for different error scenarios: - `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 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? @@ -777,10 +305,6 @@ A: Use the `setResumeSessionId()` method in `TransportOptions` to resume a previ 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. diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 8786a78cc..c11385395 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -29,6 +29,11 @@ 5.14.1 1.3.16 2.0.60 + 3.13.0 + 9 + 2 + 2.9.1 + 1.5 @@ -67,34 +72,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - 3.13.0 - - 1.8 - 1.8 - - - - compile-examples - compile - - compile - - - 1.8 - 1.8 - - com/alibaba/qwen/code/example/**/*.java - - - ${project.basedir}/src/example/java - - - - - org.apache.maven.plugins maven-checkstyle-plugin @@ -132,7 +109,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.9.0 + 0.${central-publishing-maven-plugin.version}.0 true central @@ -142,7 +119,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + ${maven-source-plugin.version}.2.1 attach-sources @@ -155,7 +132,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 + ${maven-javadoc-plugin.version} attach-javadocs @@ -168,7 +145,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.5 + ${maven-gpg-plugin.version} sign-artifacts diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java index 6571f5de3..9a654034d 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/QwenCodeCli.java @@ -12,8 +12,9 @@ import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssist import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation; import com.alibaba.qwen.code.cli.session.Session; -import com.alibaba.qwen.code.cli.session.event.AssistantContentConsumers; -import com.alibaba.qwen.code.cli.session.event.SessionEventSimpleConsumers; +import com.alibaba.qwen.code.cli.session.event.consumers.AssistantContentConsumers; +import com.alibaba.qwen.code.cli.session.event.consumers.AssistantContentSimpleConsumers; +import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventSimpleConsumers; import com.alibaba.qwen.code.cli.transport.Transport; import com.alibaba.qwen.code.cli.transport.TransportOptions; import com.alibaba.qwen.code.cli.transport.process.ProcessTransport; @@ -51,7 +52,7 @@ public class QwenCodeCli { */ public static List simpleQuery(String prompt, TransportOptions transportOptions) { final List response = new ArrayList<>(); - MyConcurrentUtils.runAndWait(() -> simpleQuery(prompt, transportOptions, new AssistantContentConsumers() { + MyConcurrentUtils.runAndWait(() -> simpleQuery(prompt, transportOptions, new AssistantContentSimpleConsumers() { @Override public void onText(Session session, TextAssistantContent textAssistantContent) { response.add(textAssistantContent.getText()); @@ -80,7 +81,7 @@ public class QwenCodeCli { public void onUsage(Session session, AssistantUsage assistantUsage) { log.info("received usage {} of message {}", assistantUsage.getUsage(), assistantUsage.getMessageId()); } - }), Timeout.TIMEOUT_30_MINUTES); + }.setDefaultPermissionOperation(Operation.allow)), Timeout.TIMEOUT_30_MINUTES); return response; } @@ -95,8 +96,7 @@ public class QwenCodeCli { Session session = newSession(transportOptions); try { session.sendPrompt(prompt, new SessionEventSimpleConsumers() - .setDefaultPermissionOperation(Operation.allow) - .setBlockConsumer(assistantContentConsumers)); + .setAssistantContentConsumer(assistantContentConsumers)); } catch (Exception e) { throw new RuntimeException("sendPrompt error!", e); } finally { diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java index 20893e044..5adca830e 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/data/behavior/Behavior.java @@ -53,6 +53,10 @@ public class Behavior { * @return The default behavior */ public static Behavior defaultBehavior() { + return denyBehavior(); + } + + public static Behavior denyBehavior() { return new Deny().setMessage("Default Behavior Permission denied"); } } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java deleted file mode 100644 index fb72114bc..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInterruptRequest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.alibaba.qwen.code.cli.protocol.message.control; - -/** - * Represents a control interrupt request to the CLI. - * - * @author skyfire - * @version $Id: 0.0.1 - */ -public class CLIControlInterruptRequest { - /** - * The subtype of the request ("interrupt"). - */ - String subtype = "interrupt"; - - /** - * Gets the subtype of the request. - * - * @return The subtype of the request - */ - public String getSubtype() { - return subtype; - } - - /** - * Sets the subtype of the request. - * - * @param subtype The subtype of the request - */ - public void setSubtype(String subtype) { - this.subtype = subtype; - } -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java index e888984be..58079bc6b 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlRequest.java @@ -5,6 +5,7 @@ import java.util.UUID; import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.message.MessageBase; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload; /** * Represents a control request to the CLI. @@ -14,7 +15,7 @@ import com.alibaba.qwen.code.cli.protocol.message.MessageBase; * @version $Id: 0.0.1 */ @JSONType(typeKey = "type", typeName = "control_request") -public class CLIControlRequest extends MessageBase { +public class CLIControlRequest extends MessageBase { /** * The ID of the request. */ @@ -41,7 +42,7 @@ public class CLIControlRequest extends MessageBase { * @param The type of the request object * @return A new control request instance */ - public static CLIControlRequest create(T request) { + public static CLIControlRequest create(T request) { CLIControlRequest controlRequest = new CLIControlRequest<>(); controlRequest.setRequest(request); return controlRequest; diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java deleted file mode 100644 index 2f2d4a751..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.alibaba.qwen.code.cli.protocol.message.control; - -/** - * Represents a control request to set the model in the CLI. - * - * @author skyfire - * @version $Id: 0.0.1 - */ -public class CLIControlSetModelRequest { - /** - * The subtype of the request ("set_model"). - */ - String subtype = "set_model"; - /** - * The model to set. - */ - String model; - - /** - * Gets the subtype of the request. - * - * @return The subtype of the request - */ - public String getSubtype() { - return subtype; - } - - /** - * Sets the subtype of the request. - * - * @param subtype The subtype of the request - */ - public void setSubtype(String subtype) { - this.subtype = subtype; - } - - /** - * Gets the model to set. - * - * @return The model to set - */ - public String getModel() { - return model; - } - - /** - * Sets the model to set. - * - * @param model The model to set - */ - public void setModel(String model) { - this.model = model; - } -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java deleted file mode 100644 index 7fb26851f..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetPermissionModeRequest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.alibaba.qwen.code.cli.protocol.message.control; - -/** - * Represents a control request to set the permission mode in the CLI. - * - * @author skyfire - * @version $Id: 0.0.1 - */ -public class CLIControlSetPermissionModeRequest { - /** - * The subtype of the request ("set_permission_mode"). - */ - String subtype = "set_permission_mode"; - - /** - * The permission mode to set. - */ - String mode; - - /** - * Gets the subtype of the request. - * - * @return The subtype of the request - */ - public String getSubtype() { - return subtype; - } - - /** - * Sets the subtype of the request. - * - * @param subtype The subtype of the request - */ - public void setSubtype(String subtype) { - this.subtype = subtype; - } - - /** - * Gets the permission mode to set. - * - * @return The permission mode to set - */ - public String getMode() { - return mode; - } - - /** - * Sets the permission mode to set. - * - * @param mode The permission mode to set - */ - public void setMode(String mode) { - this.mode = mode; - } -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInitializeRequest.java similarity index 63% rename from packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java rename to packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInitializeRequest.java index 64b5ffc78..a990e0316 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInitializeRequest.java @@ -1,6 +1,7 @@ -package com.alibaba.qwen.code.cli.protocol.message.control; +package com.alibaba.qwen.code.cli.protocol.message.control.payload; import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.data.InitializeConfig; /** @@ -9,11 +10,12 @@ import com.alibaba.qwen.code.cli.protocol.data.InitializeConfig; * @author skyfire * @version $Id: 0.0.1 */ -public class CLIControlInitializeRequest { - /** - * The subtype of the request. - */ - String subtype = "initialize"; +@JSONType(typeKey = "subtype", typeName = "initialize") +public class CLIControlInitializeRequest extends ControlRequestPayload { + public CLIControlInitializeRequest() { + super(); + this.subtype = "initialize"; + } /** * The initialization configuration. @@ -21,24 +23,6 @@ public class CLIControlInitializeRequest { @JSONField(unwrapped = true) InitializeConfig initializeConfig = new InitializeConfig(); - /** - * Gets the subtype of the request. - * - * @return The subtype of the request - */ - public String getSubtype() { - return subtype; - } - - /** - * Sets the subtype of the request. - * - * @param subtype The subtype of the request - */ - public void setSubtype(String subtype) { - this.subtype = subtype; - } - /** * Gets the initialization configuration. * diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInitializeResponse.java similarity index 55% rename from packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java rename to packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInitializeResponse.java index 2216de169..aabeec016 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlInitializeResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInitializeResponse.java @@ -1,5 +1,6 @@ -package com.alibaba.qwen.code.cli.protocol.message.control; +package com.alibaba.qwen.code.cli.protocol.message.control.payload; +import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.data.Capabilities; /** @@ -8,34 +9,18 @@ import com.alibaba.qwen.code.cli.protocol.data.Capabilities; * @author skyfire * @version $Id: 0.0.1 */ -public class CLIControlInitializeResponse { - /** - * The subtype of the response. - */ - String subtype = "initialize"; +@JSONType(typeKey = "subtype", typeName = "initialize") +public class CLIControlInitializeResponse extends ControlResponsePayload { + public CLIControlInitializeResponse() { + super(); + this.subtype = "initialize"; + } + /** * The capabilities' information. */ Capabilities capabilities; - /** - * Gets the subtype of the response. - * - * @return The subtype of the response - */ - public String getSubtype() { - return subtype; - } - - /** - * Sets the subtype of the response. - * - * @param subtype The subtype of the response - */ - public void setSubtype(String subtype) { - this.subtype = subtype; - } - /** * Gets the capabilities information. * diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInterruptRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInterruptRequest.java new file mode 100644 index 000000000..cf3f83567 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlInterruptRequest.java @@ -0,0 +1,17 @@ +package com.alibaba.qwen.code.cli.protocol.message.control.payload; + +import com.alibaba.fastjson2.annotation.JSONType; + +/** + * Represents a control interrupt request to the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 + */ +@JSONType(typeKey = "subtype", typeName = "interrupt") +public class CLIControlInterruptRequest extends ControlRequestPayload { + public CLIControlInterruptRequest() { + super(); + setSubtype("interrupt"); + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlPermissionRequest.java similarity index 90% rename from packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java rename to packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlPermissionRequest.java index 2d488c96c..e15133dfb 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionRequest.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlPermissionRequest.java @@ -1,9 +1,10 @@ -package com.alibaba.qwen.code.cli.protocol.message.control; +package com.alibaba.qwen.code.cli.protocol.message.control.payload; import java.util.List; import java.util.Map; import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; /** * Represents a control permission request to the CLI. @@ -11,11 +12,12 @@ import com.alibaba.fastjson2.annotation.JSONField; * @author skyfire * @version $Id: 0.0.1 */ -public class CLIControlPermissionRequest { - /** - * The subtype of the request. - */ - private String subtype; +@JSONType(typeKey = "subtype", typeName = "can_use_tool") +public class CLIControlPermissionRequest extends ControlRequestPayload { + public CLIControlPermissionRequest() { + super(); + this.subtype = "can_use_tool"; + } /** * The name of the tool requesting permission. @@ -46,24 +48,6 @@ public class CLIControlPermissionRequest { @JSONField(name = "blocked_path") private String blockedPath; - /** - * Gets the subtype of the request. - * - * @return The subtype of the request - */ - public String getSubtype() { - return subtype; - } - - /** - * Sets the subtype of the request. - * - * @param subtype The subtype of the request - */ - public void setSubtype(String subtype) { - this.subtype = subtype; - } - /** * Gets the name of the tool requesting permission. * diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlPermissionResponse.java similarity index 60% rename from packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java rename to packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlPermissionResponse.java index c5b23fe24..771f0d581 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlPermissionResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlPermissionResponse.java @@ -1,6 +1,7 @@ -package com.alibaba.qwen.code.cli.protocol.message.control; +package com.alibaba.qwen.code.cli.protocol.message.control.payload; import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.annotation.JSONType; import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; /** @@ -9,11 +10,12 @@ import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; * @author skyfire * @version $Id: 0.0.1 */ -public class CLIControlPermissionResponse { - /** - * The subtype of the response ("can_use_tool"). - */ - private String subtype = "can_use_tool"; +@JSONType(typeKey = "subtype", typeName = "can_use_tool") +public class CLIControlPermissionResponse extends ControlResponsePayload { + public CLIControlPermissionResponse() { + super(); + this.subtype = "can_use_tool"; + } /** * The behavior for the permission request. @@ -21,24 +23,6 @@ public class CLIControlPermissionResponse { @JSONField(unwrapped = true) Behavior behavior; - /** - * Gets the subtype of the response. - * - * @return The subtype of the response - */ - public String getSubtype() { - return subtype; - } - - /** - * Sets the subtype of the response. - * - * @param subtype The subtype of the response - */ - public void setSubtype(String subtype) { - this.subtype = subtype; - } - /** * Gets the behavior for the permission request. * diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetModelRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetModelRequest.java new file mode 100644 index 000000000..e12b704a3 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetModelRequest.java @@ -0,0 +1,40 @@ +package com.alibaba.qwen.code.cli.protocol.message.control.payload; + +import com.alibaba.fastjson2.annotation.JSONType; + +/** + * Represents a control request to set the model in the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 + */ +@JSONType(typeKey = "subtype", typeName = "set_model") +public class CLIControlSetModelRequest extends ControlRequestPayload { + public CLIControlSetModelRequest() { + super(); + this.subtype = "set_model"; + } + + /** + * The model to set. + */ + String model; + + /** + * Gets the model to set. + * + * @return The model to set + */ + public String getModel() { + return model; + } + + /** + * Sets the model to set. + * + * @param model The model to set + */ + public void setModel(String model) { + this.model = model; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetModelResponse.java similarity index 93% rename from packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java rename to packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetModelResponse.java index 6c3dc2c2c..b59552e0c 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/CLIControlSetModelResponse.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetModelResponse.java @@ -1,4 +1,4 @@ -package com.alibaba.qwen.code.cli.protocol.message.control; +package com.alibaba.qwen.code.cli.protocol.message.control.payload; /** * Represents a control response for setting the model in the CLI. diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetPermissionModeRequest.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetPermissionModeRequest.java new file mode 100644 index 000000000..3e5ef9dd1 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/CLIControlSetPermissionModeRequest.java @@ -0,0 +1,40 @@ +package com.alibaba.qwen.code.cli.protocol.message.control.payload; + +import com.alibaba.fastjson2.annotation.JSONType; + +/** + * Represents a control request to set the permission mode in the CLI. + * + * @author skyfire + * @version $Id: 0.0.1 + */ +@JSONType(typeKey = "subtype", typeName = "set_permission_mode") +public class CLIControlSetPermissionModeRequest extends ControlRequestPayload { + public CLIControlSetPermissionModeRequest() { + super(); + setSubtype("set_permission_mode"); + } + + /** + * The permission mode to set. + */ + String mode; + + /** + * Gets the permission mode to set. + * + * @return The permission mode to set + */ + public String getMode() { + return mode; + } + + /** + * Sets the permission mode to set. + * + * @param mode The permission mode to set + */ + public void setMode(String mode) { + this.mode = mode; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/ControlRequestPayload.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/ControlRequestPayload.java new file mode 100644 index 000000000..1390850e7 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/ControlRequestPayload.java @@ -0,0 +1,26 @@ +package com.alibaba.qwen.code.cli.protocol.message.control.payload; + +import com.alibaba.fastjson2.annotation.JSONType; + +/** + * Represents a payload request in the CLI control message. + * + * @author skyfire + * @version $Id: 0.0.1 + */ +@JSONType(typeKey = "subtype", typeName = "ControlRequestPayload", + seeAlso = {CLIControlInitializeRequest.class, CLIControlInterruptRequest.class, CLIControlPermissionRequest.class, CLIControlSetModelRequest.class, CLIControlSetPermissionModeRequest.class}) +public class ControlRequestPayload { + /** + * The subtype of the request. + */ + protected String subtype; + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/ControlResponsePayload.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/ControlResponsePayload.java new file mode 100644 index 000000000..fe8cdd8ae --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/protocol/message/control/payload/ControlResponsePayload.java @@ -0,0 +1,26 @@ +package com.alibaba.qwen.code.cli.protocol.message.control.payload; + +import com.alibaba.fastjson2.annotation.JSONType; + +/** + * Represents a payload request in the CLI control message. + * + * @author skyfire + * @version $Id: 0.0.1 + */ +@JSONType(typeKey = "subtype", typeName = "ControlResponsePayload", + seeAlso = {CLIControlInitializeResponse.class, CLIControlPermissionResponse.class}) +public class ControlResponsePayload { + /** + * The subtype of the request. + */ + protected String subtype; + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java index 95a6cb6ba..e72ab6a04 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/Session.java @@ -1,9 +1,6 @@ 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; @@ -11,23 +8,21 @@ 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.payload.CLIControlInitializeRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlInitializeResponse; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlInterruptRequest; import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest; import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse; -import com.alibaba.qwen.code.cli.protocol.message.control.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.protocol.message.control.payload.CLIControlSetModelRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlSetPermissionModeRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload; +import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventConsumers; import com.alibaba.qwen.code.cli.session.exception.SessionControlException; import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException; import com.alibaba.qwen.code.cli.transport.Transport; @@ -204,29 +199,37 @@ public class Session { if ("system".equals(messageType)) { lastSdkSystemMessage = jsonObject.to(SDKSystemMessage.class); MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onSystemMessage(this, lastSdkSystemMessage), - Optional.ofNullable(sessionEventConsumers.onSystemMessageTimeout(this)).orElse(defaultEventTimeout)); + Optional.ofNullable(sessionEventConsumers.onSystemMessageTimeout(this, lastSdkSystemMessage)) + .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)); + SDKAssistantMessage assistantMessage = jsonObject.to(SDKAssistantMessage.class); + MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onAssistantMessage(this, assistantMessage), + Optional.ofNullable(sessionEventConsumers.onAssistantMessageTimeout(this, assistantMessage)).orElse(defaultEventTimeout)); return false; } else if ("stream_event".equals(messageType)) { + SDKPartialAssistantMessage sdkPartialAssistantMessage = jsonObject.to(SDKPartialAssistantMessage.class); MyConcurrentUtils.runAndWait( - () -> sessionEventConsumers.onPartialAssistantMessage(this, jsonObject.to(SDKPartialAssistantMessage.class)), - Optional.ofNullable(sessionEventConsumers.onPartialAssistantMessageTimeout(this)).orElse(defaultEventTimeout)); + () -> sessionEventConsumers.onPartialAssistantMessage(this, sdkPartialAssistantMessage), + Optional.ofNullable(sessionEventConsumers.onPartialAssistantMessageTimeout(this, sdkPartialAssistantMessage)) + .orElse(defaultEventTimeout)); return false; } else if ("user".equals(messageType)) { + SDKUserMessage sdkUserMessage = jsonObject.to(SDKUserMessage.class, Feature.FieldBased); MyConcurrentUtils.runAndWait( - () -> sessionEventConsumers.onUserMessage(this, jsonObject.to(SDKUserMessage.class, Feature.FieldBased)), - Optional.ofNullable(sessionEventConsumers.onUserMessageTimeout(this)).orElse(defaultEventTimeout)); + () -> sessionEventConsumers.onUserMessage(this, sdkUserMessage), + Optional.ofNullable(sessionEventConsumers.onUserMessageTimeout(this, sdkUserMessage)).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)); + SDKResultMessage sdkResultMessage = jsonObject.to(SDKResultMessage.class); + MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onResultMessage(this, sdkResultMessage), + Optional.ofNullable(sessionEventConsumers.onResultMessageTimeout(this, sdkResultMessage)).orElse(defaultEventTimeout)); return true; } else if ("control_response".equals(messageType)) { - MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onControlResponse(this, jsonObject.to(CLIControlResponse.class)), - Optional.ofNullable(sessionEventConsumers.onControlResponseTimeout(this)).orElse(defaultEventTimeout)); + CLIControlResponse controlResponse = jsonObject.to( + new TypeReference>() {}); + MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onControlResponse(this, controlResponse), + Optional.ofNullable(sessionEventConsumers.onControlResponseTimeout(this, controlResponse)).orElse(defaultEventTimeout)); if (!"error".equals(jsonObject.getString("subtype"))) { return false; } else { @@ -234,11 +237,28 @@ public class Session { return "error".equals(jsonObject.getString("subtype")); } } else if ("control_request".equals(messageType)) { - return processControlRequestInThePrompting(jsonObject, sessionEventConsumers); + CLIControlResponse controlResponse; + try { + CLIControlRequest controlRequest = jsonObject.to( + new TypeReference>() {}); + controlResponse = MyConcurrentUtils.runAndWait( + () -> sessionEventConsumers.onControlRequest(this, controlRequest), + Optional.ofNullable(sessionEventConsumers.onControlRequestTimeout(this, controlRequest)).orElse(defaultEventTimeout)); + } catch (Exception e) { + log.error("Failed to process control request", e); + controlResponse = new CLIControlResponse<>(); + } + try { + transport.inputNoWaitResponse(Optional.ofNullable(controlResponse).map(CLIControlResponse::toString) + .orElse(new CLIControlResponse().toString())); + } catch (Exception e) { + throw new RuntimeException("Failed to send control response", e); + } + return false; } else { log.warn("unknown message type: {}", messageType); MyConcurrentUtils.runAndWait(() -> sessionEventConsumers.onOtherMessage(this, line), - Optional.ofNullable(sessionEventConsumers.onOtherMessageTimeout(this)).orElse(defaultEventTimeout)); + Optional.ofNullable(sessionEventConsumers.onOtherMessageTimeout(this, line)).orElse(defaultEventTimeout)); return false; } }); @@ -247,68 +267,6 @@ public class Session { } } - 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>() {})), - 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 permissionRequest = jsonObject.to( - new TypeReference>() {}); - - 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 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; - } - /** * Gets the current session ID. * diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java deleted file mode 100644 index 8fce1c4fa..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentConsumers.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.alibaba.qwen.code.cli.session.event; - -import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; -import com.alibaba.qwen.code.cli.session.Session; - -/** - * Interface for handling different types of assistant content during a session. - * - * @author skyfire - * @version $Id: 0.0.1 - */ -public interface AssistantContentConsumers { - /** - * Handles text content from the assistant. - * - * @param session The session - * @param textAssistantContent The text content from the assistant - */ - void onText(Session session, TextAssistantContent textAssistantContent); - - /** - * Handles thinking content from the assistant. - * - * @param session The session - * @param thingkingAssistantContent The thinking content from the assistant - */ - void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent); - - /** - * Handles tool use content from the assistant. - * - * @param session The session - * @param toolUseAssistantContent The tool use content from the assistant - */ - void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent); - - /** - * Handles tool result content from the assistant. - * - * @param session The session - * @param toolResultAssistantContent The tool result content from the assistant - */ - void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent); - - /** - * Handles other types of assistant content. - * - * @param session The session - * @param other The other content from the assistant - */ - void onOtherContent(Session session, AssistantContent other); - - /** - * Handles usage information from the assistant. - * - * @param session The session - * @param AssistantUsage The usage information from the assistant - */ - void onUsage(Session session, AssistantUsage AssistantUsage); -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java deleted file mode 100644 index 9a2b63df9..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/AssistantContentSimpleConsumers.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.alibaba.qwen.code.cli.session.event; - -import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; -import com.alibaba.qwen.code.cli.session.Session; - -/** - * Simple implementation of AssistantContentConsumers that provides empty implementations for all methods. - * - * @author skyfire - * @version $Id: 0.0.1 - */ -public class AssistantContentSimpleConsumers implements AssistantContentConsumers { - /** {@inheritDoc} */ - @Override - public void onText(Session session, TextAssistantContent textAssistantContent) { - } - - /** {@inheritDoc} */ - @Override - public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { - } - - /** {@inheritDoc} */ - @Override - public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) { - } - - /** {@inheritDoc} */ - @Override - public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) { - } - - /** {@inheritDoc} */ - @Override - public void onOtherContent(Session session, AssistantContent other) { - } - - /** {@inheritDoc} */ - @Override - public void onUsage(Session session, AssistantUsage AssistantUsage) { - } -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java deleted file mode 100644 index 38ed31aa1..000000000 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventSimpleConsumers.java +++ /dev/null @@ -1,266 +0,0 @@ -package com.alibaba.qwen.code.cli.session.event; - -import java.util.List; - -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; -import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow; -import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; -import com.alibaba.qwen.code.cli.protocol.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.block.ContentBlock; -import com.alibaba.qwen.code.cli.protocol.message.assistant.event.ContentBlockDeltaEvent; -import com.alibaba.qwen.code.cli.protocol.message.assistant.event.StreamEvent; -import com.alibaba.qwen.code.cli.protocol.message.control.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; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Simple implementation of SessionEventConsumers that provides basic implementations for all methods. - * - * @author skyfire - * @version $Id: 0.0.1 - */ -public class SessionEventSimpleConsumers implements SessionEventConsumers { - /** {@inheritDoc} */ - @Override - public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { - } - - /** {@inheritDoc} */ - @Override - public void onResultMessage(Session session, SDKResultMessage resultMessage) { - } - - /** {@inheritDoc} */ - @Override - public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) { - List> contentBlocks = assistantMessage.getMessage().getContent(); - if (assistantContentConsumers == null || contentBlocks == null || contentBlocks.isEmpty()) { - return; - } - assistantContentConsumers.onUsage(session, new AssistantUsage(assistantMessage.getMessage().getId(), assistantMessage.getMessage().getUsage())); - - if (!session.isStreaming()) { - contentBlocks.forEach(contentBlock -> consumeAssistantContent(session, contentBlock)); - } - } - - /** {@inheritDoc} */ - @Override - public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) { - StreamEvent event = partialAssistantMessage.getEvent(); - if (!(event instanceof ContentBlockDeltaEvent)) { - log.debug("received partialAssistantMessage and is not instance of ContentBlockDeltaEvent, will ignore process. the message is {}", - partialAssistantMessage); - return; - } - ContentBlockDeltaEvent contentBlockDeltaEvent = (ContentBlockDeltaEvent) event; - contentBlockDeltaEvent.getDelta().setMessageId(partialAssistantMessage.getMessageId()); - consumeAssistantContent(session, contentBlockDeltaEvent.getDelta()); - } - - /** - *

consumeAssistantContent.

- * - * @param session a {@link com.alibaba.qwen.code.cli.session.Session} object. - * @param assistantContent a {@link com.alibaba.qwen.code.cli.protocol.data.AssistantContent} object. - */ - protected void consumeAssistantContent(Session session, AssistantContent assistantContent) { - if (assistantContent instanceof TextAssistantContent) { - assistantContentConsumers.onText(session, (TextAssistantContent) assistantContent); - } else if (assistantContent instanceof ThingkingAssistantContent) { - assistantContentConsumers.onThinking(session, (ThingkingAssistantContent) assistantContent); - } else if (assistantContent instanceof ToolUseAssistantContent) { - assistantContentConsumers.onToolUse(session, (ToolUseAssistantContent) assistantContent); - } else if (assistantContent instanceof ToolResultAssistantContent) { - assistantContentConsumers.onToolResult(session, (ToolResultAssistantContent) assistantContent); - } else { - assistantContentConsumers.onOtherContent(session, assistantContent); - } - } - - /** {@inheritDoc} */ - @Override - public void onUserMessage(Session session, SDKUserMessage userMessage) { - } - - /** {@inheritDoc} */ - @Override - public void onOtherMessage(Session session, String message) { - } - - /** {@inheritDoc} */ - @Override - public void onControlResponse(Session session, CLIControlResponse cliControlResponse) { - } - - /** {@inheritDoc} */ - @Override - public CLIControlResponse onControlRequest(Session session, CLIControlRequest cliControlRequest) { - return new CLIControlResponse<>(); - } - - /** {@inheritDoc} */ - @Override - public Behavior onPermissionRequest(Session session, CLIControlRequest permissionRequest) { - if (Operation.deny.equals(this.defaultPermissionOperation)) { - return new Deny().setMessage("Permission denied."); - } else { - return new Allow().setUpdatedInput(permissionRequest.getRequest().getInput()); - } - } - - /** {@inheritDoc} */ - @Override - public Timeout onSystemMessageTimeout(Session session) { - return defaultEventTimeout; - } - - /** {@inheritDoc} */ - @Override - public Timeout onResultMessageTimeout(Session session) { - return defaultEventTimeout; - } - - /** {@inheritDoc} */ - @Override - public Timeout onAssistantMessageTimeout(Session session) { - return defaultEventTimeout; - } - - /** {@inheritDoc} */ - @Override - public Timeout onPartialAssistantMessageTimeout(Session session) { - return defaultEventTimeout; - } - - /** {@inheritDoc} */ - @Override - public Timeout onUserMessageTimeout(Session session) { - return defaultEventTimeout; - } - - /** {@inheritDoc} */ - @Override - public Timeout onOtherMessageTimeout(Session session) { - return defaultEventTimeout; - } - - /** {@inheritDoc} */ - @Override - public Timeout onControlResponseTimeout(Session session) { - return defaultEventTimeout; - } - - /** {@inheritDoc} */ - @Override - public Timeout onControlRequestTimeout(Session session) { - return defaultEventTimeout; - } - - /** {@inheritDoc} */ - @Override - public Timeout onPermissionRequestTimeout(Session session) { - return defaultEventTimeout; - } - - /** - * Gets the default event timeout. - * - * @return The default event timeout - */ - protected Timeout getDefaultEventTimeout() { - return defaultEventTimeout; - } - - /** - * Sets the default event timeout. - * - * @param defaultEventTimeout The default event timeout - * @return This instance for method chaining - */ - public SessionEventSimpleConsumers setDefaultEventTimeout(Timeout defaultEventTimeout) { - this.defaultEventTimeout = defaultEventTimeout; - return this; - } - - /** - * Gets the default permission operation. - * - * @return The default permission operation - */ - protected Operation getDefaultPermissionOperation() { - return defaultPermissionOperation; - } - - /** - * Sets the default permission operation. - * - * @param defaultPermissionOperation The default permission operation - * @return This instance for method chaining - */ - public SessionEventSimpleConsumers setDefaultPermissionOperation(Operation defaultPermissionOperation) { - this.defaultPermissionOperation = defaultPermissionOperation; - return this; - } - - /** - * Creates a new SessionEventSimpleConsumers instance with default values. - */ - public SessionEventSimpleConsumers() { - } - - /** - * Creates a new SessionEventSimpleConsumers instance with the specified parameters. - * - * @param defaultPermissionOperation The default permission operation - * @param defaultEventTimeout The default event timeout - * @param assistantContentConsumers The assistant content consumers - */ - public SessionEventSimpleConsumers(Operation defaultPermissionOperation, Timeout defaultEventTimeout, - AssistantContentConsumers assistantContentConsumers) { - this.defaultPermissionOperation = defaultPermissionOperation; - this.defaultEventTimeout = defaultEventTimeout; - this.assistantContentConsumers = assistantContentConsumers; - } - - /** - * The default permission operation. - */ - private Operation defaultPermissionOperation = Operation.deny; - /** - * The default event timeout. - */ - protected Timeout defaultEventTimeout = Timeout.TIMEOUT_60_SECONDS; - /** - * The assistant content consumers. - */ - protected AssistantContentConsumers assistantContentConsumers; - private static final Logger log = LoggerFactory.getLogger(SessionEventSimpleConsumers.class); - - /** - * Sets the assistant content consumers. - * - * @param assistantContentConsumers The assistant content consumers - * @return This instance for method chaining - */ - public SessionEventSimpleConsumers setBlockConsumer(AssistantContentConsumers assistantContentConsumers) { - this.assistantContentConsumers = assistantContentConsumers; - return this; - } -} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentConsumers.java new file mode 100644 index 000000000..233bf7353 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentConsumers.java @@ -0,0 +1,159 @@ +package com.alibaba.qwen.code.cli.session.event.consumers; + +import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlPermissionRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload; +import com.alibaba.qwen.code.cli.session.Session; +import com.alibaba.qwen.code.cli.utils.Timeout; + +/** + * Interface for handling different types of assistant content during a session. + * + * @author skyfire + * @version $Id: 0.0.1 + */ +public interface AssistantContentConsumers { + /** + * Handles text content from the assistant. + * + * @param session The session + * @param textAssistantContent The text content from the assistant + */ + void onText(Session session, TextAssistantContent textAssistantContent); + + /** + * Handles thinking content from the assistant. + * + * @param session The session + * @param thingkingAssistantContent The thinking content from the assistant + */ + void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent); + + /** + * Handles tool use content from the assistant. + * + * @param session The session + * @param toolUseAssistantContent The tool use content from the assistant + */ + void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent); + + /** + * Handles tool result content from the assistant. + * + * @param session The session + * @param toolResultAssistantContent The tool result content from the assistant + */ + void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent); + + /** + * Handles other types of assistant content. + * + * @param session The session + * @param other The other content from the assistant + */ + void onOtherContent(Session session, AssistantContent other); + + /** + * Handles permission requests. + * + * @param session The session + * @param permissionRequest The permission request + * @return The behavior for the permission request + */ + Behavior onPermissionRequest(Session session, CLIControlPermissionRequest permissionRequest); + + /** + * Handles permission requests. + * + * @param session The session + * @param requestPayload The control request payload + * @return The response payload for the control request + */ + ControlResponsePayload onOtherControlRequest(Session session, ControlRequestPayload requestPayload); + + /** + * Handles usage information from the assistant. + * + * @param session The session + * @param AssistantUsage The usage information from the assistant + */ + void onUsage(Session session, AssistantUsage AssistantUsage); + + /** + * Sets the default permission operation. + * + * @param defaultPermissionOperation The default permission operation + * @return This instance for method chaining + */ + AssistantContentSimpleConsumers setDefaultPermissionOperation(Operation defaultPermissionOperation); + + /** + * Gets timeout for permission request handling. + * + * @param session The session + * @return The timeout for permission request handling + */ + Timeout onPermissionRequestTimeout(Session session, CLIControlPermissionRequest permissionRequest); + + /** + * Gets timeout for other control request handling. + * + * @param session The session + * @param requestPayload The control request payload + * @return The timeout for other control request handling + */ + Timeout onOtherControlRequestTimeout(Session session, ControlRequestPayload requestPayload); + + /** + * Gets timeout for text handling. + * + * @param session The session + * @param textAssistantContent The text content from the assistant + * @return The timeout for text handling + */ + Timeout onTextTimeout(Session session, TextAssistantContent textAssistantContent); + + /** + * Gets timeout for thinking handling. + * + * @param session The session + * @param thingkingAssistantContent The thinking content from the assistant + * @return The timeout for thinking handling + */ + Timeout onThinkingTimeout(Session session, ThingkingAssistantContent thingkingAssistantContent); + + /** + * Gets timeout for tool use handling. + * + * @param session The session + * @param toolUseAssistantContent The tool use content from the assistant + * @return The timeout for tool use handling + */ + Timeout onToolUseTimeout(Session session, ToolUseAssistantContent toolUseAssistantContent); + + /** + * Gets timeout for tool result handling. + * + * @param session The session + * @param toolResultAssistantContent The tool result content from the assistant + * @return The timeout for tool result handling + */ + Timeout onToolResultTimeout(Session session, ToolResultAssistantContent toolResultAssistantContent); + + /** + * Gets timeout for other content handling. + * + * @param session The session + * @param other The other content from the assistant + * @return The timeout for other content handling + */ + Timeout onOtherContentTimeout(Session session, AssistantContent other); +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentSimpleConsumers.java new file mode 100644 index 000000000..40c941e32 --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentSimpleConsumers.java @@ -0,0 +1,176 @@ +package com.alibaba.qwen.code.cli.session.event.consumers; + +import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Deny; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlPermissionRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload; +import com.alibaba.qwen.code.cli.session.Session; +import com.alibaba.qwen.code.cli.utils.Timeout; + +/** + * Simple implementation of AssistantContentConsumers that provides empty implementations for all methods. + * + * @author skyfire + * @version $Id: 0.0.1 + */ +public class AssistantContentSimpleConsumers implements AssistantContentConsumers { + /** + * {@inheritDoc} + */ + @Override + public void onText(Session session, TextAssistantContent textAssistantContent) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onOtherContent(Session session, AssistantContent other) { + } + + /** + * {@inheritDoc} + */ + @Override + public Behavior onPermissionRequest(Session session, CLIControlPermissionRequest permissionRequest) { + if (Operation.deny.equals(this.defaultPermissionOperation)) { + return new Deny().setMessage("Permission denied."); + } else { + return new Allow().setUpdatedInput(permissionRequest.getInput()); + } + } + + @Override + public ControlResponsePayload onOtherControlRequest(Session session, ControlRequestPayload requestPayload) { + throw new RuntimeException("need override onOtherControlRequest"); + } + + /** + * {@inheritDoc} + */ + @Override + public void onUsage(Session session, AssistantUsage AssistantUsage) { + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onPermissionRequestTimeout(Session session, CLIControlPermissionRequest permissionRequest) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onOtherControlRequestTimeout(Session session, ControlRequestPayload requestPayload) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onTextTimeout(Session session, TextAssistantContent textAssistantContent) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onThinkingTimeout(Session session, ThingkingAssistantContent thingkingAssistantContent) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onToolUseTimeout(Session session, ToolUseAssistantContent toolUseAssistantContent) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onToolResultTimeout(Session session, ToolResultAssistantContent toolResultAssistantContent) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onOtherContentTimeout(Session session, AssistantContent other) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public AssistantContentSimpleConsumers setDefaultPermissionOperation(Operation defaultPermissionOperation) { + this.defaultPermissionOperation = defaultPermissionOperation; + return this; + } + + /** + * Constructor. + * + * @param defaultPermissionOperation The default permission operation. + * @param defaultEventTimeout The default event timeout. + */ + public AssistantContentSimpleConsumers(Operation defaultPermissionOperation, Timeout defaultEventTimeout) { + this.defaultPermissionOperation = defaultPermissionOperation; + this.defaultEventTimeout = defaultEventTimeout; + } + + /** + * Constructor. + */ + public AssistantContentSimpleConsumers() { + } + + /** + * The default permission operation. + */ + private Operation defaultPermissionOperation = Operation.deny; + + /** + * The default event timeout. + */ + protected Timeout defaultEventTimeout = Timeout.TIMEOUT_60_SECONDS; +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/SessionEventConsumers.java similarity index 71% rename from packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java rename to packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/SessionEventConsumers.java index 7159beaef..ba37ca641 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/SessionEventConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/SessionEventConsumers.java @@ -1,14 +1,14 @@ -package com.alibaba.qwen.code.cli.session.event; +package com.alibaba.qwen.code.cli.session.event.consumers; -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.protocol.message.control.payload.ControlRequestPayload; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload; import com.alibaba.qwen.code.cli.session.Session; import com.alibaba.qwen.code.cli.utils.Timeout; @@ -82,86 +82,77 @@ public interface SessionEventConsumers { * @param cliControlRequest The control request * @return The control response */ - CLIControlResponse onControlRequest(Session session, CLIControlRequest cliControlRequest); - - /** - * Handles permission requests. - * - * @param session The session - * @param permissionRequest The permission request - * @return The behavior for the permission request - */ - Behavior onPermissionRequest(Session session, CLIControlRequest permissionRequest); + CLIControlResponse onControlRequest(Session session, CLIControlRequest cliControlRequest); /** * Gets timeout for system message handling. * * @param session The session + * @param systemMessage The system message * @return The timeout for system message handling */ - Timeout onSystemMessageTimeout(Session session); + Timeout onSystemMessageTimeout(Session session, SDKSystemMessage systemMessage); /** * Gets timeout for result message handling. * * @param session The session + * @param resultMessage The result message * @return The timeout for result message handling */ - Timeout onResultMessageTimeout(Session session); + Timeout onResultMessageTimeout(Session session, SDKResultMessage resultMessage); /** * Gets timeout for assistant message handling. * * @param session The session + * @param assistantMessage The assistant message * @return The timeout for assistant message handling */ - Timeout onAssistantMessageTimeout(Session session); + Timeout onAssistantMessageTimeout(Session session, SDKAssistantMessage assistantMessage); /** * Gets timeout for partial assistant message handling. * * @param session The session + * @param partialAssistantMessage The partial assistant message * @return The timeout for partial assistant message handling */ - Timeout onPartialAssistantMessageTimeout(Session session); + Timeout onPartialAssistantMessageTimeout(Session session, SDKPartialAssistantMessage partialAssistantMessage); /** * Gets timeout for user message handling. * * @param session The session + * @param userMessage The user message * @return The timeout for user message handling */ - Timeout onUserMessageTimeout(Session session); + Timeout onUserMessageTimeout(Session session, SDKUserMessage userMessage); /** * Gets timeout for other message handling. * * @param session The session + * @param message The message * @return The timeout for other message handling */ - Timeout onOtherMessageTimeout(Session session); + Timeout onOtherMessageTimeout(Session session, String message); /** * Gets timeout for control response handling. * * @param session The session + * @param cliControlResponse The control response * @return The timeout for control response handling */ - Timeout onControlResponseTimeout(Session session); + Timeout onControlResponseTimeout(Session session, CLIControlResponse cliControlResponse); /** * Gets timeout for control request handling. * * @param session The session + * @param cliControlRequest The control request * @return The timeout for control request handling */ - Timeout onControlRequestTimeout(Session session); - - /** - * Gets timeout for permission request handling. - * - * @param session The session - * @return The timeout for permission request handling - */ - Timeout onPermissionRequestTimeout(Session session); + Timeout onControlRequestTimeout(Session session, CLIControlRequest cliControlRequest); } diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/SessionEventSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/SessionEventSimpleConsumers.java new file mode 100644 index 000000000..e3d9e47cc --- /dev/null +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/SessionEventSimpleConsumers.java @@ -0,0 +1,339 @@ +package com.alibaba.qwen.code.cli.session.event.consumers; + +import java.util.List; +import java.util.Optional; + +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; +import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage; +import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage; +import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKPartialAssistantMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.block.ContentBlock; +import com.alibaba.qwen.code.cli.protocol.message.assistant.event.ContentBlockDeltaEvent; +import com.alibaba.qwen.code.cli.protocol.message.assistant.event.StreamEvent; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlPermissionRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlPermissionResponse; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload; +import com.alibaba.qwen.code.cli.session.Session; +import com.alibaba.qwen.code.cli.utils.MyConcurrentUtils; +import com.alibaba.qwen.code.cli.utils.Timeout; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple implementation of SessionEventConsumers that provides basic implementations for all methods. + * + * @author skyfire + * @version $Id: 0.0.1 + */ +public class SessionEventSimpleConsumers implements SessionEventConsumers { + /** + * {@inheritDoc} + */ + @Override + public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onResultMessage(Session session, SDKResultMessage resultMessage) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) { + List> contentBlocks = assistantMessage.getMessage().getContent(); + if (assistantContentConsumers == null || contentBlocks == null || contentBlocks.isEmpty()) { + return; + } + assistantContentConsumers.onUsage(session, + new AssistantUsage(assistantMessage.getMessage().getId(), assistantMessage.getMessage().getUsage())); + + if (!session.isStreaming()) { + contentBlocks.forEach(contentBlock -> consumeAssistantContent(session, contentBlock)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) { + StreamEvent event = partialAssistantMessage.getEvent(); + if (!(event instanceof ContentBlockDeltaEvent)) { + log.debug("received partialAssistantMessage and is not instance of ContentBlockDeltaEvent, will ignore process. the message is {}", + partialAssistantMessage); + return; + } + ContentBlockDeltaEvent contentBlockDeltaEvent = (ContentBlockDeltaEvent) event; + contentBlockDeltaEvent.getDelta().setMessageId(partialAssistantMessage.getMessageId()); + consumeAssistantContent(session, contentBlockDeltaEvent.getDelta()); + } + + /** + *

consumeAssistantContent.

+ * + * @param session a {@link com.alibaba.qwen.code.cli.session.Session} object. + * @param assistantContent a {@link com.alibaba.qwen.code.cli.protocol.data.AssistantContent} object. + */ + protected void consumeAssistantContent(Session session, AssistantContent assistantContent) { + if (assistantContent instanceof TextAssistantContent) { + MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onText(session, (TextAssistantContent) assistantContent), + Optional.ofNullable(assistantContentConsumers.onTextTimeout(session, (TextAssistantContent) assistantContent)) + .orElse(defaultEventTimeout)); + } else if (assistantContent instanceof ThingkingAssistantContent) { + MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onThinking(session, (ThingkingAssistantContent) assistantContent), + Optional.ofNullable(assistantContentConsumers.onThinkingTimeout(session, (ThingkingAssistantContent) assistantContent)) + .orElse(defaultEventTimeout)); + } else if (assistantContent instanceof ToolUseAssistantContent) { + MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onToolUse(session, (ToolUseAssistantContent) assistantContent), + Optional.ofNullable(assistantContentConsumers.onToolUseTimeout(session, (ToolUseAssistantContent) assistantContent)) + .orElse(defaultEventTimeout)); + } else if (assistantContent instanceof ToolResultAssistantContent) { + MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onToolResult(session, (ToolResultAssistantContent) assistantContent), + Optional.ofNullable(assistantContentConsumers.onToolResultTimeout(session, (ToolResultAssistantContent) assistantContent)) + .orElse(defaultEventTimeout)); + } else { + MyConcurrentUtils.runAndWait(() -> assistantContentConsumers.onOtherContent(session, assistantContent), + Optional.ofNullable(assistantContentConsumers.onOtherContentTimeout(session, assistantContent)).orElse(defaultEventTimeout)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onUserMessage(Session session, SDKUserMessage userMessage) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onOtherMessage(Session session, String message) { + } + + /** + * {@inheritDoc} + */ + @Override + public void onControlResponse(Session session, CLIControlResponse cliControlResponse) { + } + + /** + * {@inheritDoc} + */ + @Override + public CLIControlResponse onControlRequest(Session session, CLIControlRequest cliControlRequest) { + if (assistantContentConsumers == null) { + throw new RuntimeException("please set assistantContentConsumers or override onControlRequest of "); + } + ControlRequestPayload payload = cliControlRequest.getRequest(); + if (payload instanceof CLIControlPermissionRequest) { + CLIControlPermissionRequest permissionRequest = (CLIControlPermissionRequest) payload; + return supplyPermissionControlResponse(session, permissionRequest, cliControlRequest.getRequestId()); + } else { + ControlRequestPayload request = cliControlRequest.getRequest(); + return supplyOtherControlResponse(session, request, cliControlRequest.getRequestId()); + } + } + + private CLIControlResponse supplyPermissionControlResponse(Session session, + CLIControlPermissionRequest permissionRequest, String requestId) { + Behavior behavior; + try { + behavior = Optional.ofNullable( + MyConcurrentUtils.runAndWait(() -> this.assistantContentConsumers.onPermissionRequest(session, permissionRequest), + Optional.ofNullable(assistantContentConsumers.onPermissionRequestTimeout(session, permissionRequest)) + .orElse(defaultEventTimeout))) + .map(b -> { + if (b instanceof Allow) { + Allow allow = (Allow) b; + if (allow.getUpdatedInput() == null) { + allow.setUpdatedInput(permissionRequest.getInput()); + } + } + return b; + }) + .orElse(Behavior.defaultBehavior()); + } catch (Exception e) { + log.error("Failed to process permission response", e); + behavior = Behavior.defaultBehavior(); + } + + CLIControlResponse permissionResponse = new CLIControlResponse<>(); + permissionResponse.createResponse().setResponse(new CLIControlPermissionResponse().setBehavior(behavior)).setRequestId(requestId); + return permissionResponse; + } + + private CLIControlResponse supplyOtherControlResponse(Session session, ControlRequestPayload requestPayload, + String requestId) { + ControlResponsePayload controlResponsePayload; + try { + controlResponsePayload = Optional.ofNullable( + MyConcurrentUtils.runAndWait(() -> this.assistantContentConsumers.onOtherControlRequest(session, requestPayload), + ObjectUtils.getIfNull(assistantContentConsumers.onOtherControlRequestTimeout(session, requestPayload), + defaultEventTimeout))) + .orElse(new ControlResponsePayload()); + } catch (Exception e) { + log.error("Failed to process permission response", e); + controlResponsePayload = new ControlResponsePayload(); + } + + CLIControlResponse cliControlResponse = new CLIControlResponse<>(); + cliControlResponse.createResponse().setResponse(controlResponsePayload).setRequestId(requestId); + return cliControlResponse; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onSystemMessageTimeout(Session session, SDKSystemMessage systemMessage) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onResultMessageTimeout(Session session, SDKResultMessage resultMessage) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onAssistantMessageTimeout(Session session, SDKAssistantMessage assistantMessage) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onPartialAssistantMessageTimeout(Session session, SDKPartialAssistantMessage partialAssistantMessage) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onUserMessageTimeout(Session session, SDKUserMessage userMessage) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onOtherMessageTimeout(Session session, String message) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onControlResponseTimeout(Session session, CLIControlResponse cliControlResponse) { + return defaultEventTimeout; + } + + /** + * {@inheritDoc} + */ + @Override + public Timeout onControlRequestTimeout(Session session, CLIControlRequest cliControlRequest) { + return defaultEventTimeout; + } + + /** + * Gets the default event timeout. + * + * @return The default event timeout + */ + protected Timeout getDefaultEventTimeout() { + return defaultEventTimeout; + } + + /** + * Sets the default event timeout. + * + * @param defaultEventTimeout The default event timeout + * @return This instance for method chaining + */ + public SessionEventSimpleConsumers setDefaultEventTimeout(Timeout defaultEventTimeout) { + this.defaultEventTimeout = defaultEventTimeout; + return this; + } + + /** + * Creates a new SessionEventSimpleConsumers instance with default values. + */ + public SessionEventSimpleConsumers() { + } + + /** + * Creates a new SessionEventSimpleConsumers instance with the specified parameters. + * + * @param defaultEventTimeout The default event timeout + * @param assistantContentConsumers The assistant content consumers + */ + public SessionEventSimpleConsumers(Timeout defaultEventTimeout, AssistantContentConsumers assistantContentConsumers) { + Validate.notNull(defaultEventTimeout, "defaultEventTimeout can't be null"); + Validate.notNull(assistantContentConsumers, "assistantContentConsumers can't be null"); + this.defaultEventTimeout = defaultEventTimeout; + this.assistantContentConsumers = assistantContentConsumers; + } + + /** + * The default event timeout. + */ + protected Timeout defaultEventTimeout = Timeout.TIMEOUT_180_SECONDS; + /** + * The assistant content consumers. + */ + protected AssistantContentConsumers assistantContentConsumers = new AssistantContentSimpleConsumers(); + private static final Logger log = LoggerFactory.getLogger(SessionEventSimpleConsumers.class); + + /** + * Sets the assistant content consumers. + * + * @param assistantContentConsumers The assistant content consumers + * @return This instance for method chaining + */ + public SessionEventSimpleConsumers setAssistantContentConsumer(AssistantContentConsumers assistantContentConsumers) { + Validate.notNull(assistantContentConsumers, "assistantContentConsumers can't be null"); + this.assistantContentConsumers = assistantContentConsumers; + return this; + } + + /** + * Gets the assistant content consumers. + * + * @return The assistant content consumers + */ + public AssistantContentConsumers getAssistantContentConsumers() { + return assistantContentConsumers; + } +} diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java index 8213cdef8..78e58a92e 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/ThreadPoolConfig.java @@ -17,7 +17,7 @@ import java.util.function.Supplier; */ public class ThreadPoolConfig { private static final ThreadPoolExecutor defaultExecutor = new ThreadPoolExecutor( - 10, 30, 60L, TimeUnit.SECONDS, + 30, 100, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(300), new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java index e221cddbb..089752723 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/utils/Timeout.java @@ -55,6 +55,12 @@ public class Timeout { * A timeout of 60 seconds. */ public static final Timeout TIMEOUT_60_SECONDS = new Timeout(60L, TimeUnit.SECONDS); + + /** + * A timeout of 180 seconds. + */ + public static final Timeout TIMEOUT_180_SECONDS = new Timeout(180L, TimeUnit.SECONDS); + /** * A timeout of 30 minutes. */ diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/QuickStartExample.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/QuickStartExample.java new file mode 100644 index 000000000..2a7a20dc6 --- /dev/null +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/QuickStartExample.java @@ -0,0 +1,109 @@ +package com.alibaba.qwen.code.cli.example; + +import com.alibaba.qwen.code.cli.QwenCodeCli; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; +import com.alibaba.qwen.code.cli.protocol.data.PermissionMode; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation; +import com.alibaba.qwen.code.cli.session.Session; +import com.alibaba.qwen.code.cli.session.event.consumers.AssistantContentSimpleConsumers; +import com.alibaba.qwen.code.cli.transport.TransportOptions; +import com.alibaba.qwen.code.cli.utils.Timeout; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class QuickStartExample { + private static final Logger logger = LoggerFactory.getLogger(QuickStartExample.class); + + public static void main(String[] args) { + logger.info("runSimpleExample started.{}", StringUtils.repeat("=", 150)); + runSimpleExample(); + + logger.info("runTransportOptionsExample started. {}", StringUtils.repeat("=", 150)); + runTransportOptionsExample(); + + logger.info("runStreamingExample started. {}", StringUtils.repeat("=", 150)); + runStreamingExample(); + + System.exit(0); + } + + /** + * Simple example showing basic query usage + */ + public static void runSimpleExample() { + List result = QwenCodeCli.simpleQuery("hello world"); + result.forEach(logger::info); + } + + /** + * TransportOptions example showing comprehensive transport options configuration + */ + public static void runTransportOptionsExample() { + TransportOptions options = new TransportOptions() + .setModel("qwen3-coder-flash") + .setPermissionMode(PermissionMode.AUTO_EDIT) + .setCwd("./") + .setEnv(new HashMap() {{put("CUSTOM_VAR", "value");}}) + .setIncludePartialMessages(true) + .setTurnTimeout(new Timeout(120L, TimeUnit.SECONDS)) + .setMessageTimeout(new Timeout(90L, TimeUnit.SECONDS)) + .setAllowedTools(Arrays.asList("read_file", "write_file", "list_directory")); + + List result = QwenCodeCli.simpleQuery("who are you, what are your capabilities?", options); + result.forEach(logger::info); + } + + /** + * Streaming example showing simple query usage + */ + public static void runStreamingExample() { + QwenCodeCli.simpleQuery("who are you, what are your capabilities?", + new TransportOptions().setMessageTimeout(new Timeout(10L, TimeUnit.SECONDS)), new AssistantContentSimpleConsumers() { + + @Override + public void onText(Session session, TextAssistantContent textAssistantContent) { + logger.info("Text content received: {}", textAssistantContent.getText()); + } + + @Override + public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { + logger.info("Thinking content received: {}", thingkingAssistantContent.getThinking()); + } + + @Override + public void onToolUse(Session session, ToolUseAssistantContent toolUseContent) { + logger.info("Tool use content received: {} with arguments: {}", + toolUseContent, toolUseContent.getInput()); + } + + @Override + public void onToolResult(Session session, ToolResultAssistantContent toolResultContent) { + logger.info("Tool result content received: {}", toolResultContent.getContent()); + } + + @Override + public void onOtherContent(Session session, AssistantContent other) { + logger.info("Other content received: {}", other); + } + + @Override + public void onUsage(Session session, AssistantUsage assistantUsage) { + logger.info("Usage information received: Input tokens: {}, Output tokens: {}", + assistantUsage.getUsage().getInputTokens(), assistantUsage.getUsage().getOutputTokens()); + } + }.setDefaultPermissionOperation(Operation.allow)); + logger.info("Streaming example completed."); + } +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/SessionExample.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/SessionExample.java new file mode 100644 index 000000000..fdbc58bdf --- /dev/null +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/SessionExample.java @@ -0,0 +1,256 @@ +package com.alibaba.qwen.code.cli.example; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.qwen.code.cli.QwenCodeCli; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; +import com.alibaba.qwen.code.cli.protocol.data.AssistantUsage; +import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation; +import com.alibaba.qwen.code.cli.protocol.message.SDKResultMessage; +import com.alibaba.qwen.code.cli.protocol.message.SDKSystemMessage; +import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKPartialAssistantMessage; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRequestPayload; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlResponsePayload; +import com.alibaba.qwen.code.cli.session.Session; +import com.alibaba.qwen.code.cli.session.event.consumers.AssistantContentSimpleConsumers; +import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventSimpleConsumers; +import com.alibaba.qwen.code.cli.protocol.data.PermissionMode; +import com.alibaba.qwen.code.cli.protocol.message.assistant.SDKAssistantMessage; +import com.alibaba.qwen.code.cli.protocol.message.assistant.block.TextBlock; +import com.alibaba.qwen.code.cli.session.exception.SessionControlException; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +public class SessionExample { + private static final Logger logger = LoggerFactory.getLogger(SessionExample.class); + + public static void main(String[] args) { + Session session = QwenCodeCli.newSession(); + try { + logger.info("runPermissionModeExample started {}", StringUtils.repeat("=", 150)); + runPermissionModeExample(session); + + logger.info("runSetModelExample started {}", StringUtils.repeat("=", 150)); + runSetModelExample(session); + + logger.info("runSetPermissionModeExample started {}", StringUtils.repeat("=", 150)); + runSetPermissionModeExample(session); + + logger.info("runInterruptExample started {}", StringUtils.repeat("=", 150)); + runInterruptExample(session); + + logger.info("runSetModelExample started {}", StringUtils.repeat("=", 150)); + runSetModelExample(session); + + logger.info("runPromptUseLowLevelEventExample started {}", StringUtils.repeat("=", 150)); + runPromptUseLowLevelEventExample(session); + + logger.info("runPromptUseHighLevelEventExample started {}", StringUtils.repeat("=", 150)); + runPromptUseHighLevelEventExample(session); + + System.exit(0); + } finally { + try { + session.close(); + } catch (SessionControlException e) { + logger.error("Error closing session", e); + } + } + } + + /** + * Example showing how to set different permission modes + */ + public static void runPermissionModeExample(Session session) { + try { + logger.info(session.setPermissionMode(PermissionMode.PLAN).map(s -> s ? "Permission mode set to PLAN" : "Permission mode set error") + .orElse("Permission mode set unknown")); + } catch (SessionControlException e) { + logger.error("Error setting permission mode", e); + } + } + + /** + * Example showing how to interrupt a running prompt + */ + public static void runInterruptExample(Session session) { + try { + session.sendPrompt("Analyze this large codebase...", new SessionEventSimpleConsumers() { + @Override + public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) { + String message = assistantMessage.getMessage().getContent().stream() + .findFirst() + .filter(content -> content instanceof TextBlock) + .map(content -> ((TextBlock) content).getText()) + .orElse(""); + logger.info("Received: {}", message); + + // Interrupt the session after receiving the first message + try { + Optional interruptResult = session.interrupt(); + logger.info("{}", interruptResult.map(s -> s ? "Interrupt successful" : "Interrupt error") + .orElse("Interrupt unknown")); + } catch (SessionControlException e) { + logger.error("Interrupt error: {}", e.getMessage(), e); + } + } + }); + } catch (Exception e) { + logger.error("An error occurred while sending the prompt", e); + } + } + + /** + * Example showing how to dynamically change the AI model during a session + */ + public static void runSetModelExample(Session session) { + try { + // Switch to a specific model + Optional modelChangeResult = session.setModel("qwen3-coder-flash"); + logger.info("{}", modelChangeResult.map(s -> s ? "setModel success" : "setModel error") + .orElse("setModel unknown")); + + // Use the model for a prompt + session.sendPrompt("hello world", new SessionEventSimpleConsumers()); + + // Switch to another model + Optional modelChangeResult2 = session.setModel("qwen3-coder-plus"); + logger.info("{}", modelChangeResult2.map(s -> s ? "setModel success" : "setModel error") + .orElse("setModel unknown")); + + // Use the new model for another prompt + session.sendPrompt("list files in the current directory", new SessionEventSimpleConsumers()); + } catch (Exception e) { + logger.error("An error occurred while changing model or sending prompt", e); + } + } + + /** + * Example showing how to dynamically change permission mode during a session + */ + public static void runSetPermissionModeExample(Session session) { + try { + // Switch to a permissive mode + Optional permissionChangeResult = session.setPermissionMode(PermissionMode.YOLO); + logger.info("{}", permissionChangeResult.map(s -> s ? "setPermissionMode success" : "setPermissionMode error") + .orElse("setPermissionMode unknown")); + + // Use the session with the new permission mode + session.sendPrompt("in the dir src/test/temp/, create file empty file test.touch", new SessionEventSimpleConsumers()); + + // Switch to another permission mode + Optional permissionChangeResult2 = session.setPermissionMode(PermissionMode.PLAN); + logger.info("{}", permissionChangeResult2.map(s -> s ? "setPermissionMode success" : "setPermissionMode error") + .orElse("setPermissionMode unknown")); + + // Use the session with the new permission mode + session.sendPrompt("rename test.touch to test_rename.touch", new SessionEventSimpleConsumers()); + } catch (Exception e) { + logger.error("An error occurred while changing permission mode or sending prompt", e); + } + } + + public static void runPromptUseLowLevelEventExample(Session session) { + try { + session.setPermissionMode(PermissionMode.YOLO); + session.sendPrompt("devlop Fibonacci function by python", new SessionEventSimpleConsumers() { + @Override + public void onAssistantMessage(Session session, SDKAssistantMessage assistantMessage) { + logger.info("Received assistantMessage {}", JSON.toJSONString(assistantMessage)); + } + + @Override + public void onPartialAssistantMessage(Session session, SDKPartialAssistantMessage partialAssistantMessage) { + logger.info("Received partialAssistantMessage {}", JSON.toJSONString(partialAssistantMessage)); + } + + @Override + public void onUserMessage(Session session, SDKUserMessage userMessage) { + logger.info("Received userMessage {}", JSON.toJSONString(userMessage)); + } + + @Override + public void onOtherMessage(Session session, String message) { + logger.info("Received otherMessage {}", message); + } + + @Override + public void onControlResponse(Session session, CLIControlResponse cliControlResponse) { + logger.info("Received controlResponse {}", JSON.toJSONString(cliControlResponse)); + } + + @Override + public CLIControlResponse onControlRequest(Session session, CLIControlRequest cliControlRequest) { + logger.info("Received controlRequest {}", JSON.toJSONString(cliControlRequest)); + return new CLIControlResponse<>(); + } + + @Override + public void onResultMessage(Session session, SDKResultMessage resultMessage) { + logger.info("Received resultMessage {}", JSON.toJSONString(resultMessage)); + } + + @Override + public void onSystemMessage(Session session, SDKSystemMessage systemMessage) { + logger.info("Received systemMessage {}", JSON.toJSONString(systemMessage)); + } + }); + } catch (Exception e) { + logger.error("An error occurred while sending prompt", e); + } + } + + public static void runPromptUseHighLevelEventExample(Session session) { + try { + session.sendPrompt("devlop Fibonacci function by python", new SessionEventSimpleConsumers().setAssistantContentConsumer(new AssistantContentSimpleConsumers(){ + @Override + public void onText(Session session, TextAssistantContent textAssistantContent) { + logger.info("Received textAssistantContent {}", textAssistantContent.getText()); + } + + @Override + public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { + logger.info("Received thingkingAssistantContent {}", thingkingAssistantContent.getThinking()); + } + + @Override + public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) { + logger.info("Received toolUseAssistantContent {}", toolUseAssistantContent.getInput()); + } + + @Override + public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) { + logger.info("Received toolResultAssistantContent {}", toolResultAssistantContent.getContent()); + } + + @Override + public void onOtherContent(Session session, AssistantContent other) { + logger.info("Received other {}", other); + } + + @Override + public void onUsage(Session session, AssistantUsage assistantUsage) { + logger.info("Received usage {}", assistantUsage); + } + + @Override + public ControlResponsePayload onOtherControlRequest(Session session, ControlRequestPayload requestPayload) { + logger.info("Received otherControlRequest {}", requestPayload); + return new ControlResponsePayload(); + } + }.setDefaultPermissionOperation(Operation.allow))); + } catch (Exception e) { + logger.error("An error occurred while sending prompt", e); + } + } +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/ThreadPoolConfigurationExample.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/ThreadPoolConfigurationExample.java new file mode 100644 index 000000000..0a77a03ad --- /dev/null +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/example/ThreadPoolConfigurationExample.java @@ -0,0 +1,50 @@ +package com.alibaba.qwen.code.cli.example; + +import com.alibaba.qwen.code.cli.QwenCodeCli; +import com.alibaba.qwen.code.cli.session.Session; +import com.alibaba.qwen.code.cli.utils.ThreadPoolConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class ThreadPoolConfigurationExample { + private static final Logger logger = LoggerFactory.getLogger(ThreadPoolConfigurationExample.class); + + public static void main(String[] args) { + runModifyDefaultExample(); + runCustomSupplierExample(); + } + + /** + * Example showing how to set a custom thread pool supplier + */ + public static void runCustomSupplierExample() { + // Set a custom thread pool supplier + ThreadPoolConfig.setExecutorSupplier(() -> (ThreadPoolExecutor) Executors.newFixedThreadPool(20)); + logger.info("Custom thread pool supplier set"); + } + + /** + * Example showing how to modify properties of the default thread pool + */ + public static void runModifyDefaultExample() { + // Get the default executor and modify its properties + ThreadPoolExecutor executor = ThreadPoolConfig.getDefaultExecutor(); + + // Modify the core pool size + executor.setCorePoolSize(15); + + // Modify the maximum pool size + executor.setMaximumPoolSize(40); + + // Modify the keep-alive time + executor.setKeepAliveTime(120, TimeUnit.SECONDS); + + logger.info("Default thread pool properties modified"); + + // The SDK will now use the modified executor for all operations + Session session = QwenCodeCli.newSession(); + } +} diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java index a6463d41d..f17060c1e 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java @@ -11,18 +11,14 @@ import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.TextAssistantCon import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ThingkingAssistantContent; import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolResultAssistantContent; import com.alibaba.qwen.code.cli.protocol.data.AssistantContent.ToolUseAssistantContent; -import com.alibaba.qwen.code.cli.protocol.data.behavior.Allow; -import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior; import com.alibaba.qwen.code.cli.protocol.data.behavior.Behavior.Operation; import com.alibaba.qwen.code.cli.protocol.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.AssistantContentConsumers; -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.event.consumers.AssistantContentSimpleConsumers; +import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventConsumers; +import com.alibaba.qwen.code.cli.session.event.consumers.SessionEventSimpleConsumers; import com.alibaba.qwen.code.cli.session.exception.SessionControlException; import com.alibaba.qwen.code.cli.session.exception.SessionSendPromptException; import com.alibaba.qwen.code.cli.transport.TransportOptions; @@ -41,7 +37,7 @@ class SessionTest { void partialSendPromptSuccessfully() throws SessionControlException, SessionSendPromptException { Session session = QwenCodeCli.newSession(new TransportOptions().setIncludePartialMessages(true)); session.sendPrompt("in the dir src/test/temp/, create file empty file test.touch", new SessionEventSimpleConsumers() { - }.setDefaultPermissionOperation(Operation.allow).setBlockConsumer(new AssistantContentConsumers() { + }.setAssistantContentConsumer(new AssistantContentSimpleConsumers() { @Override public void onText(Session session, TextAssistantContent textAssistantContent) { log.info("receive textAssistantContent {}", textAssistantContent); @@ -70,7 +66,7 @@ class SessionTest { public void onUsage(Session session, AssistantUsage assistantUsage) { log.info("receive assistantUsage {}", assistantUsage); } - })); + }.setDefaultPermissionOperation(Operation.allow))); } @Test @@ -89,12 +85,8 @@ class SessionTest { .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 permissionRequest) { - log.info("permissionRequest: {}", permissionRequest); - return new Allow().setUpdatedInput(permissionRequest.getRequest().getInput()); - } - }); + session.sendPrompt("rename test.touch to test_rename.touch again user will allow", + new SessionEventSimpleConsumers().setAssistantContentConsumer(new AssistantContentSimpleConsumers().setDefaultPermissionOperation(Operation.allow))); session.close(); } @@ -112,13 +104,13 @@ class SessionTest { 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()); + session.sendPrompt("Check how many files are in the current directory", new SessionEventSimpleConsumers()); writeSplitLine("prompt 2 end"); log.info(session.setModel("qwen3-max").map(s -> s ? "setModel 3 success" : "setModel 3 error").orElse("setModel 3 unknown")); writeSplitLine("setModel 1 end"); - session.sendPrompt("查看下当前目录有多少个xml文件", new SessionEventSimpleConsumers()); + session.sendPrompt("Check how many xml files are in the current directory", new SessionEventSimpleConsumers()); writeSplitLine("prompt 3 end"); session.close(); @@ -159,13 +151,9 @@ class SessionTest { 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); + + session.sendPrompt("Check how many files are in the current directory", sessionEventConsumers); writeSplitLine("prompt 1 end"); session.continueSession(); diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java index 721b203db..2dd30ae6c 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java @@ -7,8 +7,8 @@ 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.payload.CLIControlInitializeRequest; +import com.alibaba.qwen.code.cli.protocol.message.control.payload.CLIControlInitializeResponse; import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlRequest; import com.alibaba.qwen.code.cli.protocol.message.control.CLIControlResponse; import com.alibaba.qwen.code.cli.protocol.message.SDKUserMessage; From 24edf32da83e49b732a3223cf14caad71ace9bc4 Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 5 Jan 2026 17:46:18 +0800 Subject: [PATCH 42/65] for README.md --- packages/sdk-java/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sdk-java/README.md b/packages/sdk-java/README.md index e9987d946..50f8ef627 100644 --- a/packages/sdk-java/README.md +++ b/packages/sdk-java/README.md @@ -13,7 +13,6 @@ The Qwen Code Java SDK is a minimum experimental SDK for programmatic access to - **Utilities**: org.apache.commons:commons-lang3 - **JSON Processing**: com.alibaba.fastjson2:fastjson2 - **Testing**: JUnit 5 (org.junit.jupiter:junit-jupiter) -- ## Installation From 2b6218e564fa903db1957f4ed84e3e8b4ce46735 Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 5 Jan 2026 17:49:43 +0800 Subject: [PATCH 43/65] for README.md --- packages/sdk-java/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/sdk-java/README.md b/packages/sdk-java/README.md index 50f8ef627..fb1bcd472 100644 --- a/packages/sdk-java/README.md +++ b/packages/sdk-java/README.md @@ -123,6 +123,8 @@ public static void runStreamingExample() { } ``` +other examples see src/test/java/com/alibaba/qwen/code/cli/example + ## Architecture The SDK follows a layered architecture: From 96080f84a631e3405c283f521a79179b9d3c34da Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 5 Jan 2026 18:00:38 +0800 Subject: [PATCH 44/65] for README.md --- .../AssistantContentSimpleConsumers.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentSimpleConsumers.java b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentSimpleConsumers.java index 40c941e32..b5a158fed 100644 --- a/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentSimpleConsumers.java +++ b/packages/sdk-java/src/main/java/com/alibaba/qwen/code/cli/session/event/consumers/AssistantContentSimpleConsumers.java @@ -16,6 +16,9 @@ import com.alibaba.qwen.code.cli.protocol.message.control.payload.ControlRespons import com.alibaba.qwen.code.cli.session.Session; import com.alibaba.qwen.code.cli.utils.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Simple implementation of AssistantContentConsumers that provides empty implementations for all methods. * @@ -28,6 +31,7 @@ public class AssistantContentSimpleConsumers implements AssistantContentConsumer */ @Override public void onText(Session session, TextAssistantContent textAssistantContent) { + log.debug("Received textAssistantContent {}", textAssistantContent.getText()); } /** @@ -35,6 +39,7 @@ public class AssistantContentSimpleConsumers implements AssistantContentConsumer */ @Override public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { + log.debug("Received thingkingAssistantContent {}", thingkingAssistantContent.getThinking()); } /** @@ -42,6 +47,7 @@ public class AssistantContentSimpleConsumers implements AssistantContentConsumer */ @Override public void onToolUse(Session session, ToolUseAssistantContent toolUseAssistantContent) { + log.debug("Received toolUseAssistantContent {}", toolUseAssistantContent.getInput()); } /** @@ -49,6 +55,9 @@ public class AssistantContentSimpleConsumers implements AssistantContentConsumer */ @Override public void onToolResult(Session session, ToolResultAssistantContent toolResultAssistantContent) { + if (log.isDebugEnabled()) { + log.debug("Received toolResultAssistantContent {}", toolResultAssistantContent); + } } /** @@ -56,6 +65,9 @@ public class AssistantContentSimpleConsumers implements AssistantContentConsumer */ @Override public void onOtherContent(Session session, AssistantContent other) { + if (log.isDebugEnabled()) { + log.debug("Received other content {}", other); + } } /** @@ -64,8 +76,10 @@ public class AssistantContentSimpleConsumers implements AssistantContentConsumer @Override public Behavior onPermissionRequest(Session session, CLIControlPermissionRequest permissionRequest) { if (Operation.deny.equals(this.defaultPermissionOperation)) { + log.info("use defaultPermissionOperation Permission denied."); return new Deny().setMessage("Permission denied."); } else { + log.info("use defaultPermissionOperation Permission allowed."); return new Allow().setUpdatedInput(permissionRequest.getInput()); } } @@ -80,6 +94,7 @@ public class AssistantContentSimpleConsumers implements AssistantContentConsumer */ @Override public void onUsage(Session session, AssistantUsage AssistantUsage) { + log.info("received usage {} of message {}", AssistantUsage.getUsage(), AssistantUsage.getMessageId()); } /** @@ -173,4 +188,6 @@ public class AssistantContentSimpleConsumers implements AssistantContentConsumer * The default event timeout. */ protected Timeout defaultEventTimeout = Timeout.TIMEOUT_60_SECONDS; + + private static final Logger log = LoggerFactory.getLogger(AssistantContentSimpleConsumers.class); } From d2d2b845c5ec021388fd98c870beb35463cd2efe Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 5 Jan 2026 18:12:48 +0800 Subject: [PATCH 45/65] for README.md --- packages/sdk-java/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sdk-java/README.md b/packages/sdk-java/README.md index fb1bcd472..772c93742 100644 --- a/packages/sdk-java/README.md +++ b/packages/sdk-java/README.md @@ -6,6 +6,7 @@ The Qwen Code Java SDK is a minimum experimental SDK for programmatic access to - Java >= 1.8 - Maven >= 3.6.0 (for building from source) +- qwen-code >= 0.5.0 ### Dependencies From 7dc7c6380d5820401c852ae8d0d2dc04a48da1e1 Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 5 Jan 2026 18:14:40 +0800 Subject: [PATCH 46/65] for pom --- packages/sdk-java/pom.xml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index c11385395..1614239d4 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -5,7 +5,7 @@ com.alibaba qwencode-sdk jar - 0.0.1-SNAPSHOT + 0.0.1 qwencode-sdk https://maven.apache.org @@ -179,10 +179,8 @@ - - - snapshots - http://mvnrepo.alibaba-inc.com/mvn/snapshots + central + https://central.sonatype.com/repository/maven-snapshots/ From a4eb3adea867862341825b6812ea13721fdfc427 Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 5 Jan 2026 19:22:50 +0800 Subject: [PATCH 47/65] for pom --- packages/sdk-java/pom.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 1614239d4..a6825ec7e 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -5,7 +5,7 @@ com.alibaba qwencode-sdk jar - 0.0.1 + 0.0.1-alpha1 qwencode-sdk https://maven.apache.org @@ -182,5 +182,9 @@ central https://central.sonatype.com/repository/maven-snapshots/ + + central + https://central.sonatype.com/service/local/staging/deploy/maven2/ + From 19f8f631b49030c61cee986fcb81d50e359c0f58 Mon Sep 17 00:00:00 2001 From: tanzhenxin Date: Mon, 5 Jan 2026 19:28:52 +0800 Subject: [PATCH 48/65] Respect 'tools.core' and 'tools.allowed' settings in non-interactive mode, for both agent execution and custom command --- packages/cli/src/config/config.test.ts | 52 +++++++++++++++++++ packages/cli/src/config/config.ts | 39 +++++++++++--- .../prompt-processors/shellProcessor.test.ts | 30 +++++++++++ .../prompt-processors/shellProcessor.ts | 24 +++++++-- packages/core/src/core/coreToolScheduler.ts | 1 - packages/core/src/index.ts | 1 + 6 files changed, 137 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 0b95f7857..6f2019e75 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -1597,6 +1597,58 @@ describe('Approval mode tool exclusion logic', () => { expect(excludedTools).toContain(WriteFileTool.Name); }); + it('should not exclude a tool explicitly allowed in tools.allowed', async () => { + process.argv = ['node', 'script.js', '-p', 'test']; + const argv = await parseArguments({} as Settings); + const settings: Settings = { + tools: { + allowed: [ShellTool.Name], + }, + }; + const extensions: Extension[] = []; + + const config = await loadCliConfig( + settings, + extensions, + new ExtensionEnablementManager( + ExtensionStorage.getUserExtensionsDir(), + argv.extensions, + ), + argv, + ); + + const excludedTools = config.getExcludeTools(); + expect(excludedTools).not.toContain(ShellTool.Name); + expect(excludedTools).toContain(EditTool.Name); + expect(excludedTools).toContain(WriteFileTool.Name); + }); + + it('should not exclude a tool explicitly allowed in tools.core', async () => { + process.argv = ['node', 'script.js', '-p', 'test']; + const argv = await parseArguments({} as Settings); + const settings: Settings = { + tools: { + core: [ShellTool.Name], + }, + }; + const extensions: Extension[] = []; + + const config = await loadCliConfig( + settings, + extensions, + new ExtensionEnablementManager( + ExtensionStorage.getUserExtensionsDir(), + argv.extensions, + ), + argv, + ); + + const excludedTools = config.getExcludeTools(); + expect(excludedTools).not.toContain(ShellTool.Name); + expect(excludedTools).toContain(EditTool.Name); + expect(excludedTools).toContain(WriteFileTool.Name); + }); + it('should exclude only shell tools in non-interactive mode with auto-edit approval mode', async () => { process.argv = [ 'node', diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 7cd7d685a..de4c30fdc 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -10,22 +10,24 @@ import { Config, DEFAULT_QWEN_EMBEDDING_MODEL, DEFAULT_MEMORY_FILE_FILTERING_OPTIONS, - EditTool, FileDiscoveryService, getCurrentGeminiMdFilename, loadServerHierarchicalMemory, setGeminiMdFilename as setServerGeminiMdFilename, - ShellTool, - WriteFileTool, resolveTelemetrySettings, FatalConfigError, Storage, InputFormat, OutputFormat, + isToolEnabled, SessionService, type ResumedSessionData, type FileFilteringOptions, type MCPServerConfig, + type ToolName, + EditTool, + ShellTool, + WriteFileTool, } from '@qwen-code/qwen-code-core'; import { extensionsCommand } from '../commands/extensions.js'; import type { Settings } from './settings.js'; @@ -818,6 +820,28 @@ export async function loadCliConfig( // However, if stream-json input is used, control can be requested via JSON messages, // so tools should not be excluded in that case. const extraExcludes: string[] = []; + const resolvedCoreTools = argv.coreTools || settings.tools?.core || []; + const resolvedAllowedTools = + argv.allowedTools || settings.tools?.allowed || []; + const isExplicitlyEnabled = (toolName: ToolName): boolean => { + if (resolvedCoreTools.length > 0) { + if (isToolEnabled(toolName, resolvedCoreTools, [])) { + return true; + } + } + if (resolvedAllowedTools.length > 0) { + if (isToolEnabled(toolName, resolvedAllowedTools, [])) { + return true; + } + } + return false; + }; + const excludeUnlessExplicit = (toolName: ToolName): void => { + if (!isExplicitlyEnabled(toolName)) { + extraExcludes.push(toolName); + } + }; + if ( !interactive && !argv.experimentalAcp && @@ -826,12 +850,15 @@ export async function loadCliConfig( switch (approvalMode) { case ApprovalMode.PLAN: case ApprovalMode.DEFAULT: - // In default non-interactive mode, all tools that require approval are excluded. - extraExcludes.push(ShellTool.Name, EditTool.Name, WriteFileTool.Name); + // In default non-interactive mode, all tools that require approval are excluded, + // unless explicitly enabled via coreTools/allowedTools. + excludeUnlessExplicit(ShellTool.Name as ToolName); + excludeUnlessExplicit(EditTool.Name as ToolName); + excludeUnlessExplicit(WriteFileTool.Name as ToolName); break; case ApprovalMode.AUTO_EDIT: // In auto-edit non-interactive mode, only tools that still require a prompt are excluded. - extraExcludes.push(ShellTool.Name); + excludeUnlessExplicit(ShellTool.Name as ToolName); break; case ApprovalMode.YOLO: // No extra excludes for YOLO mode. diff --git a/packages/cli/src/services/prompt-processors/shellProcessor.test.ts b/packages/cli/src/services/prompt-processors/shellProcessor.test.ts index cbfca4d2d..151faf324 100644 --- a/packages/cli/src/services/prompt-processors/shellProcessor.test.ts +++ b/packages/cli/src/services/prompt-processors/shellProcessor.test.ts @@ -72,6 +72,7 @@ describe('ShellProcessor', () => { getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT), getShouldUseNodePtyShell: vi.fn().mockReturnValue(false), getShellExecutionConfig: vi.fn().mockReturnValue({}), + getAllowedTools: vi.fn().mockReturnValue([]), }; context = createMockCommandContext({ @@ -196,6 +197,35 @@ describe('ShellProcessor', () => { ); }); + it('should NOT throw ConfirmationRequiredError when a command matches allowedTools', async () => { + const processor = new ShellProcessor('test-command'); + const prompt: PromptPipelineContent = createPromptPipelineContent( + 'Do something dangerous: !{rm -rf /}', + ); + mockCheckCommandPermissions.mockReturnValue({ + allAllowed: false, + disallowedCommands: ['rm -rf /'], + }); + (mockConfig.getAllowedTools as Mock).mockReturnValue([ + 'ShellTool(rm -rf /)', + ]); + mockShellExecute.mockReturnValue({ + result: Promise.resolve({ ...SUCCESS_RESULT, output: 'deleted' }), + }); + + const result = await processor.process(prompt, context); + + expect(mockShellExecute).toHaveBeenCalledWith( + 'rm -rf /', + expect.any(String), + expect.any(Function), + expect.any(Object), + false, + expect.any(Object), + ); + expect(result).toEqual([{ text: 'Do something dangerous: deleted' }]); + }); + it('should NOT throw ConfirmationRequiredError if a command is not allowed but approval mode is YOLO', async () => { const processor = new ShellProcessor('test-command'); const prompt: PromptPipelineContent = createPromptPipelineContent( diff --git a/packages/cli/src/services/prompt-processors/shellProcessor.ts b/packages/cli/src/services/prompt-processors/shellProcessor.ts index c10526e62..2a6df7161 100644 --- a/packages/cli/src/services/prompt-processors/shellProcessor.ts +++ b/packages/cli/src/services/prompt-processors/shellProcessor.ts @@ -7,11 +7,13 @@ import { ApprovalMode, checkCommandPermissions, + doesToolInvocationMatch, escapeShellArg, getShellConfiguration, ShellExecutionService, flatMapTextParts, } from '@qwen-code/qwen-code-core'; +import type { AnyToolInvocation } from '@qwen-code/qwen-code-core'; import type { CommandContext } from '../../ui/commands/types.js'; import type { IPromptProcessor, PromptPipelineContent } from './types.js'; @@ -124,6 +126,15 @@ export class ShellProcessor implements IPromptProcessor { // Security check on the final, escaped command string. const { allAllowed, disallowedCommands, blockReason, isHardDenial } = checkCommandPermissions(command, config, sessionShellAllowlist); + const allowedTools = config.getAllowedTools() || []; + const invocation = { + params: { command }, + } as AnyToolInvocation; + const isAllowedBySettings = doesToolInvocationMatch( + 'run_shell_command', + invocation, + allowedTools, + ); if (!allAllowed) { if (isHardDenial) { @@ -132,10 +143,17 @@ export class ShellProcessor implements IPromptProcessor { ); } - // If not a hard denial, respect YOLO mode and auto-approve. - if (config.getApprovalMode() !== ApprovalMode.YOLO) { - disallowedCommands.forEach((uc) => commandsToConfirm.add(uc)); + // If the command is allowed by settings, skip confirmation. + if (isAllowedBySettings) { + continue; } + + // If not a hard denial, respect YOLO mode and auto-approve. + if (config.getApprovalMode() === ApprovalMode.YOLO) { + continue; + } + + disallowedCommands.forEach((uc) => commandsToConfirm.add(uc)); } } diff --git a/packages/core/src/core/coreToolScheduler.ts b/packages/core/src/core/coreToolScheduler.ts index aeffdfc78..c7e2806ac 100644 --- a/packages/core/src/core/coreToolScheduler.ts +++ b/packages/core/src/core/coreToolScheduler.ts @@ -824,7 +824,6 @@ export class CoreToolScheduler { */ const shouldAutoDeny = !this.config.isInteractive() && - !this.config.getIdeMode() && !this.config.getExperimentalZedIntegration() && this.config.getInputFormat() !== InputFormat.STREAM_JSON; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 56680403b..7f7bd115b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -38,6 +38,7 @@ export * from './utils/quotaErrorDetection.js'; export * from './utils/fileUtils.js'; export * from './utils/retry.js'; export * from './utils/shell-utils.js'; +export * from './utils/tool-utils.js'; export * from './utils/terminalSerializer.js'; export * from './utils/systemEncoding.js'; export * from './utils/textUtils.js'; From e8625658ba46901bbd6fba7c8358e86bf7cfaee7 Mon Sep 17 00:00:00 2001 From: skyfire Date: Mon, 5 Jan 2026 20:27:37 +0800 Subject: [PATCH 49/65] publish 0.0.1-alpha --- packages/sdk-java/pom.xml | 7 +++++-- .../com/alibaba/qwen/code/cli/session/SessionTest.java | 2 +- .../code/cli/transport/process/ProcessTransportTest.java | 7 ++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index a6825ec7e..0c5270d1e 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -5,8 +5,11 @@ com.alibaba qwencode-sdk jar - 0.0.1-alpha1 + 0.0.1-alpha qwencode-sdk + The Qwen Code Java SDK is a minimum experimental SDK for programmatic access to Qwen Code functionality. It provides a Java interface + to interact with the Qwen Code CLI, allowing developers to integrate Qwen Code capabilities into their Java applications. + https://maven.apache.org @@ -184,7 +187,7 @@ central - https://central.sonatype.com/service/local/staging/deploy/maven2/ + https://central.sonatype.org/service/local/staging/deploy/maven2/ diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java index f17060c1e..0353d0065 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/session/SessionTest.java @@ -161,7 +161,7 @@ class SessionTest { writeSplitLine("prompt 2 end"); session.continueSession(); - session.sendPrompt("当前目录有多少个java文件", sessionEventConsumers); + session.sendPrompt("How many Java files are in the current directory", sessionEventConsumers); writeSplitLine("prompt 3 end"); session.close(); diff --git a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java index 2dd30ae6c..a23800ada 100644 --- a/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java +++ b/packages/sdk-java/src/test/java/com/alibaba/qwen/code/cli/transport/process/ProcessTransportTest.java @@ -63,17 +63,18 @@ class ProcessTransportTest { return "result".equals(JSON.parseObject(line).getString("type")); }); - String userMessage2 = new SDKUserMessage().setSessionId(sessionId).setContent("请使用中文").toString(); + String userMessage2 = new SDKUserMessage().setSessionId(sessionId).setContent("Please respond in Chinese").toString(); transport.inputWaitForMultiLine(userMessage2, line -> { return "result".equals(JSON.parseObject(line).getString("type")); }); - String userMessage3 = new SDKUserMessage().setSessionId(sessionId).setContent("当前工作区有多少个文件").toString(); + + String userMessage3 = new SDKUserMessage().setSessionId(sessionId).setContent("How many files are there in the current workspace").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(); + String userMessage4 = new SDKUserMessage().setSessionId("session-sec" + UUID.randomUUID()).setContent("How many XML files are there").toString(); transport.inputWaitForMultiLine(userMessage4, line -> { return "result".equals(JSON.parseObject(line).getString("type")); }); From d1a3e828b79e7ecbe0fa13215860b6030f5c2dad Mon Sep 17 00:00:00 2001 From: skyfire Date: Tue, 6 Jan 2026 09:21:58 +0800 Subject: [PATCH 50/65] add license --- packages/sdk-java/LICENSE | 201 ++++++++++++++++++++++++++++++++++++++ packages/sdk-java/QWEN.md | 2 +- 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 packages/sdk-java/LICENSE diff --git a/packages/sdk-java/LICENSE b/packages/sdk-java/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/packages/sdk-java/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/sdk-java/QWEN.md b/packages/sdk-java/QWEN.md index fab09cf5c..4fedee46f 100644 --- a/packages/sdk-java/QWEN.md +++ b/packages/sdk-java/QWEN.md @@ -113,7 +113,7 @@ The project uses Checkstyle for code formatting and style enforcement. The confi ### Documentation -- API documentation should follow JavaDoc conventions +- API documentation should follow Javadoc conventions - Update README files when adding new features - Include examples in documentation From c6ae0a8be79f7758ee59772344df0237b465191b Mon Sep 17 00:00:00 2001 From: skyfire Date: Tue, 6 Jan 2026 11:16:47 +0800 Subject: [PATCH 51/65] for alpha stage --- packages/sdk-java/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 0c5270d1e..6e7fa921b 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -33,7 +33,7 @@ 1.3.16 2.0.60 3.13.0 - 9 + 0.8.0 2 2.9.1 1.5 @@ -112,7 +112,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.${central-publishing-maven-plugin.version}.0 + ${central-publishing-maven-plugin.version} true central From 731fd99800629a202912228ee4a97acc1bc30626 Mon Sep 17 00:00:00 2001 From: Weaxs <459312872@qq.com> Date: Tue, 6 Jan 2026 14:21:42 +0800 Subject: [PATCH 52/65] remove duplicate reasoning_content handle && remove extra_body.enable_thinking --- .../core/src/core/openaiContentGenerator/converter.ts | 11 ----------- .../core/src/core/openaiContentGenerator/pipeline.ts | 5 ----- 2 files changed, 16 deletions(-) diff --git a/packages/core/src/core/openaiContentGenerator/converter.ts b/packages/core/src/core/openaiContentGenerator/converter.ts index 184ba5493..be7804ec8 100644 --- a/packages/core/src/core/openaiContentGenerator/converter.ts +++ b/packages/core/src/core/openaiContentGenerator/converter.ts @@ -696,17 +696,6 @@ export class OpenAIContentConverter { parts.push({ text: choice.message.content }); } - // Handle reasoning content - const message = choice.message as typeof choice.message & { - reasoning_content?: string; - }; - if (message.reasoning_content) { - parts.push({ - text: message.reasoning_content, - thought: true, - } as unknown as Part); - } - // Handle tool calls if (choice.message.tool_calls) { for (const toolCall of choice.message.tool_calls) { diff --git a/packages/core/src/core/openaiContentGenerator/pipeline.ts b/packages/core/src/core/openaiContentGenerator/pipeline.ts index 0eab0c2d2..ba483fe5f 100644 --- a/packages/core/src/core/openaiContentGenerator/pipeline.ts +++ b/packages/core/src/core/openaiContentGenerator/pipeline.ts @@ -247,11 +247,6 @@ export class ContentGenerationPipeline { request.config?.thinkingConfig && request.config.thinkingConfig.includeThoughts ) { - ( - baseRequest as OpenAI.Chat.ChatCompletionCreateParams & { - extra_body?: Record; - } - ).extra_body = { enable_thinking: true }; ( baseRequest as OpenAI.Chat.ChatCompletionCreateParams & { enable_thinking?: boolean; From 1d16513e27c1de418486ca7fa3793e6b79d60e15 Mon Sep 17 00:00:00 2001 From: Weaxs <459312872@qq.com> Date: Tue, 6 Jan 2026 14:21:42 +0800 Subject: [PATCH 53/65] remove duplicate reasoning_content handle && remove extra_body.enable_thinking --- .../core/src/core/openaiContentGenerator/converter.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/core/src/core/openaiContentGenerator/converter.ts b/packages/core/src/core/openaiContentGenerator/converter.ts index be7804ec8..5d1feb316 100644 --- a/packages/core/src/core/openaiContentGenerator/converter.ts +++ b/packages/core/src/core/openaiContentGenerator/converter.ts @@ -803,17 +803,6 @@ export class OpenAIContentConverter { } } - // Handle reasoning content - const delta = choice.delta as typeof choice.delta & { - reasoning_content?: string; - }; - if (delta.reasoning_content) { - parts.push({ - text: delta.reasoning_content, - thought: true, - }); - } - // Handle tool calls using the streaming parser if (choice.delta?.tool_calls) { for (const toolCall of choice.delta.tool_calls) { From 35bf5ef4d0cf27b7c725afb4a824ffcf3b03d854 Mon Sep 17 00:00:00 2001 From: Weaxs <459312872@qq.com> Date: Tue, 6 Jan 2026 14:37:49 +0800 Subject: [PATCH 54/65] remove duplicate reasoning_content handle --- packages/core/src/core/openaiContentGenerator/converter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/core/openaiContentGenerator/converter.ts b/packages/core/src/core/openaiContentGenerator/converter.ts index 5d1feb316..5ec7a4935 100644 --- a/packages/core/src/core/openaiContentGenerator/converter.ts +++ b/packages/core/src/core/openaiContentGenerator/converter.ts @@ -752,7 +752,7 @@ export class OpenAIContentConverter { usage.prompt_tokens_details?.cached_tokens ?? extendedUsage.cached_tokens ?? 0; - const reasoningTokens = + const thinkingTokens = usage.completion_tokens_details?.reasoning_tokens || 0; // If we only have total tokens but no breakdown, estimate the split @@ -771,7 +771,7 @@ export class OpenAIContentConverter { candidatesTokenCount: finalCompletionTokens, totalTokenCount: totalTokens, cachedContentTokenCount: cachedTokens, - thoughtsTokenCount: reasoningTokens, + thoughtsTokenCount: thinkingTokens, }; } From e2d6ab9b7e5323c772c7a98afd5eec678fd2e375 Mon Sep 17 00:00:00 2001 From: xwj02155382 Date: Tue, 6 Jan 2026 16:46:56 +0800 Subject: [PATCH 55/65] refactor: simplify background shell command handling - Remove ineffective error detection for background processes (stdio is detached/ignored, so cumulativeOutput is always empty) - Add kill command hints for both Windows and macOS/Linux - Simplify code from 40 lines to 12 lines with clearer logic - Add explanatory comment about why startup errors cannot be reliably detected --- packages/core/src/tools/shell.ts | 44 +++++++------------------------- 1 file changed, 9 insertions(+), 35 deletions(-) diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index b4cbb195b..d7afae599 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -229,43 +229,17 @@ export class ShellToolInvocation extends BaseToolInvocation< } if (shouldRunInBackground) { - // Check for obvious startup errors from captured output - const outputStr = - typeof cumulativeOutput === 'string' - ? cumulativeOutput - : JSON.stringify(cumulativeOutput); - - const errorPatterns = [ - 'is not recognized as an internal or external command', - 'The system cannot find the path specified', - 'Access is denied', - 'command not found', - 'No such file or directory', - 'Permission denied', - ]; - - const hasEarlyError = errorPatterns.some((pat) => - outputStr.includes(pat), - ); - - if (hasEarlyError) { - return { - llmContent: `Command failed to start: ${outputStr}`, - returnDisplay: `Command failed to start: ${outputStr}`, - error: { - type: ToolErrorType.EXECUTION_FAILED, - message: `Command failed to start: ${outputStr}`, - }, - }; - } - + // For background tasks, return immediately with PID info + // Note: We cannot reliably detect startup errors for background processes + // since their stdio is typically detached/ignored const pidMsg = pid ? ` PID: ${pid}` : ''; - const winHint = isWindows - ? ' (Note: Use taskkill /F /T /PID to stop)' - : ''; + const killHint = isWindows + ? ' (Use taskkill /F /T /PID to stop)' + : ' (Use kill to stop)'; + return { - llmContent: `Background command started.${pidMsg}${winHint}`, - returnDisplay: `Background command started.${pidMsg}${winHint}`, + llmContent: `Background command started.${pidMsg}${killHint}`, + returnDisplay: `Background command started.${pidMsg}${killHint}`, }; } From 8f3bbef5754dfd31775badb33be115ff59f6d538 Mon Sep 17 00:00:00 2001 From: skyfire Date: Tue, 6 Jan 2026 17:11:47 +0800 Subject: [PATCH 56/65] add qwencode-sdk java doc --- docs/developers/_meta.ts | 1 + docs/developers/sdk-java.md | 312 ++++++++++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 docs/developers/sdk-java.md diff --git a/docs/developers/_meta.ts b/docs/developers/_meta.ts index 956e1ad98..154ce1848 100644 --- a/docs/developers/_meta.ts +++ b/docs/developers/_meta.ts @@ -11,6 +11,7 @@ export default { type: 'separator', }, 'sdk-typescript': 'Typescript SDK', + 'sdk-java': 'Java SDK(alpha)', 'Dive Into Qwen Code': { title: 'Dive Into Qwen Code', type: 'separator', diff --git a/docs/developers/sdk-java.md b/docs/developers/sdk-java.md new file mode 100644 index 000000000..772c93742 --- /dev/null +++ b/docs/developers/sdk-java.md @@ -0,0 +1,312 @@ +# Qwen Code Java SDK + +The Qwen Code Java SDK is a minimum experimental SDK for programmatic access to Qwen Code functionality. It provides a Java interface to interact with the Qwen Code CLI, allowing developers to integrate Qwen Code capabilities into their Java applications. + +## Requirements + +- Java >= 1.8 +- Maven >= 3.6.0 (for building from source) +- qwen-code >= 0.5.0 + +### Dependencies + +- **Logging**: ch.qos.logback:logback-classic +- **Utilities**: org.apache.commons:commons-lang3 +- **JSON Processing**: com.alibaba.fastjson2:fastjson2 +- **Testing**: JUnit 5 (org.junit.jupiter:junit-jupiter) + +## Installation + +Add the following dependency to your Maven `pom.xml`: + +```xml + + com.alibaba + qwencode-sdk + {$version} + +``` + +Or if using Gradle, add to your `build.gradle`: + +```gradle +implementation 'com.alibaba:qwencode-sdk:{$version}' +``` + +## Building and Running + +### Build Commands + +```bash +# Compile the project +mvn compile + +# Run tests +mvn test + +# Package the JAR +mvn package + +# Install to local repository +mvn install +``` + +## Quick Start + +The simplest way to use the SDK is through the `QwenCodeCli.simpleQuery()` method: + +```java +public static void runSimpleExample() { + List result = QwenCodeCli.simpleQuery("hello world"); + result.forEach(logger::info); +} +``` + +For more advanced usage with custom transport options: + +```java +public static void runTransportOptionsExample() { + TransportOptions options = new TransportOptions() + .setModel("qwen3-coder-flash") + .setPermissionMode(PermissionMode.AUTO_EDIT) + .setCwd("./") + .setEnv(new HashMap() {{put("CUSTOM_VAR", "value");}}) + .setIncludePartialMessages(true) + .setTurnTimeout(new Timeout(120L, TimeUnit.SECONDS)) + .setMessageTimeout(new Timeout(90L, TimeUnit.SECONDS)) + .setAllowedTools(Arrays.asList("read_file", "write_file", "list_directory")); + + List result = QwenCodeCli.simpleQuery("who are you, what are your capabilities?", options); + result.forEach(logger::info); +} +``` + +For streaming content handling with custom content consumers: + +```java +public static void runStreamingExample() { + QwenCodeCli.simpleQuery("who are you, what are your capabilities?", + new TransportOptions().setMessageTimeout(new Timeout(10L, TimeUnit.SECONDS)), new AssistantContentSimpleConsumers() { + + @Override + public void onText(Session session, TextAssistantContent textAssistantContent) { + logger.info("Text content received: {}", textAssistantContent.getText()); + } + + @Override + public void onThinking(Session session, ThingkingAssistantContent thingkingAssistantContent) { + logger.info("Thinking content received: {}", thingkingAssistantContent.getThinking()); + } + + @Override + public void onToolUse(Session session, ToolUseAssistantContent toolUseContent) { + logger.info("Tool use content received: {} with arguments: {}", + toolUseContent, toolUseContent.getInput()); + } + + @Override + public void onToolResult(Session session, ToolResultAssistantContent toolResultContent) { + logger.info("Tool result content received: {}", toolResultContent.getContent()); + } + + @Override + public void onOtherContent(Session session, AssistantContent other) { + logger.info("Other content received: {}", other); + } + + @Override + public void onUsage(Session session, AssistantUsage assistantUsage) { + logger.info("Usage information received: Input tokens: {}, Output tokens: {}", + assistantUsage.getUsage().getInputTokens(), assistantUsage.getUsage().getOutputTokens()); + } + }.setDefaultPermissionOperation(Operation.allow)); + logger.info("Streaming example completed."); +} +``` + +other examples see src/test/java/com/alibaba/qwen/code/cli/example + +## Architecture + +The SDK follows a layered architecture: + +- **API Layer**: Provides the main entry points through `QwenCodeCli` class with simple static methods for basic usage +- **Session Layer**: Manages communication sessions with the Qwen Code CLI through the `Session` class +- **Transport Layer**: Handles the communication mechanism between the SDK and CLI process (currently using process transport via `ProcessTransport`) +- **Protocol Layer**: Defines data structures for communication based on the CLI protocol +- **Utils**: Common utilities for concurrent execution, timeout handling, and error management + +## Key Features + +### Permission Modes + +The SDK supports different permission modes for controlling tool execution: + +- **`default`**: Write tools are denied unless approved via `canUseTool` callback or in `allowedTools`. Read-only tools execute without confirmation. +- **`plan`**: Blocks all write tools, instructing AI to present a plan first. +- **`auto-edit`**: Auto-approve edit tools (edit, write_file) while other tools require confirmation. +- **`yolo`**: All tools execute automatically without confirmation. + +### Session Event Consumers and Assistant Content Consumers + +The SDK provides two key interfaces for handling events and content from the CLI: + +#### SessionEventConsumers Interface + +The `SessionEventConsumers` interface provides callbacks for different types of messages during a session: + +- `onSystemMessage`: Handles system messages from the CLI (receives Session and SDKSystemMessage) +- `onResultMessage`: Handles result messages from the CLI (receives Session and SDKResultMessage) +- `onAssistantMessage`: Handles assistant messages (AI responses) (receives Session and SDKAssistantMessage) +- `onPartialAssistantMessage`: Handles partial assistant messages during streaming (receives Session and SDKPartialAssistantMessage) +- `onUserMessage`: Handles user messages (receives Session and SDKUserMessage) +- `onOtherMessage`: Handles other types of messages (receives Session and String message) +- `onControlResponse`: Handles control responses (receives Session and CLIControlResponse) +- `onControlRequest`: Handles control requests (receives Session and CLIControlRequest, returns CLIControlResponse) +- `onPermissionRequest`: Handles permission requests (receives Session and CLIControlRequest, returns Behavior) + +#### AssistantContentConsumers Interface + +The `AssistantContentConsumers` interface handles different types of content within assistant messages: + +- `onText`: Handles text content (receives Session and TextAssistantContent) +- `onThinking`: Handles thinking content (receives Session and ThingkingAssistantContent) +- `onToolUse`: Handles tool use content (receives Session and ToolUseAssistantContent) +- `onToolResult`: Handles tool result content (receives Session and ToolResultAssistantContent) +- `onOtherContent`: Handles other content types (receives Session and AssistantContent) +- `onUsage`: Handles usage information (receives Session and AssistantUsage) +- `onPermissionRequest`: Handles permission requests (receives Session and CLIControlPermissionRequest, returns Behavior) +- `onOtherControlRequest`: Handles other control requests (receives Session and ControlRequestPayload, returns ControlResponsePayload) + +#### Relationship Between the Interfaces + +**Important Note on Event Hierarchy:** + +- `SessionEventConsumers` is the **high-level** event processor that handles different message types (system, assistant, user, etc.) +- `AssistantContentConsumers` is the **low-level** content processor that handles different types of content within assistant messages (text, tools, thinking, etc.) + +**Processor Relationship:** + +- `SessionEventConsumers` → `AssistantContentConsumers` (SessionEventConsumers uses AssistantContentConsumers to process content within assistant messages) + +**Event Derivation Relationships:** + +- `onAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent`, `onUsage` +- `onPartialAssistantMessage` → `onText`, `onThinking`, `onToolUse`, `onToolResult`, `onOtherContent` +- `onControlRequest` → `onPermissionRequest`, `onOtherControlRequest` + +**Event Timeout Relationships:** + +Each event handler method has a corresponding timeout method that allows customizing the timeout behavior for that specific event: + +- `onSystemMessage` ↔ `onSystemMessageTimeout` +- `onResultMessage` ↔ `onResultMessageTimeout` +- `onAssistantMessage` ↔ `onAssistantMessageTimeout` +- `onPartialAssistantMessage` ↔ `onPartialAssistantMessageTimeout` +- `onUserMessage` ↔ `onUserMessageTimeout` +- `onOtherMessage` ↔ `onOtherMessageTimeout` +- `onControlResponse` ↔ `onControlResponseTimeout` +- `onControlRequest` ↔ `onControlRequestTimeout` + +For AssistantContentConsumers timeout methods: + +- `onText` ↔ `onTextTimeout` +- `onThinking` ↔ `onThinkingTimeout` +- `onToolUse` ↔ `onToolUseTimeout` +- `onToolResult` ↔ `onToolResultTimeout` +- `onOtherContent` ↔ `onOtherContentTimeout` +- `onPermissionRequest` ↔ `onPermissionRequestTimeout` +- `onOtherControlRequest` ↔ `onOtherControlRequestTimeout` + +**Default Timeout Values:** + +- `SessionEventSimpleConsumers` default timeout: 180 seconds (Timeout.TIMEOUT_180_SECONDS) +- `AssistantContentSimpleConsumers` default timeout: 60 seconds (Timeout.TIMEOUT_60_SECONDS) + +**Timeout Hierarchy Requirements:** + +For proper operation, the following timeout relationships should be maintained: + +- `onAssistantMessageTimeout` return value should be greater than `onTextTimeout`, `onThinkingTimeout`, `onToolUseTimeout`, `onToolResultTimeout`, and `onOtherContentTimeout` return values +- `onControlRequestTimeout` return value should be greater than `onPermissionRequestTimeout` and `onOtherControlRequestTimeout` return values + +### Transport Options + +The `TransportOptions` class allows configuration of how the SDK communicates with the Qwen Code CLI: + +- `pathToQwenExecutable`: Path to the Qwen Code CLI executable +- `cwd`: Working directory for the CLI process +- `model`: AI model to use for the session +- `permissionMode`: Permission mode that controls tool execution +- `env`: Environment variables to pass to the CLI process +- `maxSessionTurns`: Limits the number of conversation turns in a session +- `coreTools`: List of core tools that should be available to the AI +- `excludeTools`: List of tools to exclude from being available to the AI +- `allowedTools`: List of tools that are pre-approved for use without additional confirmation +- `authType`: Authentication type to use for the session +- `includePartialMessages`: Enables receiving partial messages during streaming responses +- `skillsEnable`: Enables or disables skills functionality for the session +- `turnTimeout`: Timeout for a complete turn of conversation +- `messageTimeout`: Timeout for individual messages within a turn +- `resumeSessionId`: ID of a previous session to resume +- `otherOptions`: Additional command-line options to pass to the CLI + +### Session Control Features + +- **Session creation**: Use `QwenCodeCli.newSession()` to create a new session with custom options +- **Session management**: The `Session` class provides methods to send prompts, handle responses, and manage session state +- **Session cleanup**: Always close sessions using `session.close()` to properly terminate the CLI process +- **Session resumption**: Use `setResumeSessionId()` in `TransportOptions` to resume a previous session +- **Session interruption**: Use `session.interrupt()` to interrupt a currently running prompt +- **Dynamic model switching**: Use `session.setModel()` to change the model during a session +- **Dynamic permission mode switching**: Use `session.setPermissionMode()` to change the permission mode during a session + +### Thread Pool Configuration + +The SDK uses a thread pool for managing concurrent operations with the following default configuration: + +- **Core Pool Size**: 30 threads +- **Maximum Pool Size**: 100 threads +- **Keep-Alive Time**: 60 seconds +- **Queue Capacity**: 300 tasks (using LinkedBlockingQueue) +- **Thread Naming**: "qwen_code_cli-pool-{number}" +- **Daemon Threads**: false +- **Rejected Execution Handler**: CallerRunsPolicy + +## Error Handling + +The SDK provides specific exception types for different error scenarios: + +- `SessionControlException`: Thrown when there's an issue with session control (creation, initialization, etc.) +- `SessionSendPromptException`: Thrown when there's an issue sending a prompt or receiving a response +- `SessionClosedException`: Thrown when attempting to use a closed session + +## FAQ / Troubleshooting + +### Q: Do I need to install the Qwen CLI separately? + +A: No, from v0.1.1, the CLI is bundled with the SDK, so no standalone CLI installation is needed. + +### Q: What Java versions are supported? + +A: The SDK requires Java 1.8 or higher. + +### Q: How do I handle long-running requests? + +A: The SDK includes timeout utilities. You can configure timeouts using the `Timeout` class in `TransportOptions`. + +### Q: Why are some tools not executing? + +A: This is likely due to permission modes. Check your permission mode settings and consider using `allowedTools` to pre-approve certain tools. + +### Q: How do I resume a previous session? + +A: Use the `setResumeSessionId()` method in `TransportOptions` to resume a previous session. + +### Q: Can I customize the environment for the CLI process? + +A: Yes, use the `setEnv()` method in `TransportOptions` to pass environment variables to the CLI process. + +## License + +Apache-2.0 - see [LICENSE](./LICENSE) for details. From ad3086f7dd836f3fe18fc8ee3e84ca5a3f4437f6 Mon Sep 17 00:00:00 2001 From: skyfire Date: Tue, 6 Jan 2026 17:18:41 +0800 Subject: [PATCH 57/65] add qwencode-sdk java doc --- docs/developers/sdk-java.md | 2 +- packages/sdk-java/pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/developers/sdk-java.md b/docs/developers/sdk-java.md index 772c93742..0b16e60a5 100644 --- a/docs/developers/sdk-java.md +++ b/docs/developers/sdk-java.md @@ -285,7 +285,7 @@ The SDK provides specific exception types for different error scenarios: ### 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. +A: yes, requires Qwen CLI 0.5.5 or higher. ### Q: What Java versions are supported? diff --git a/packages/sdk-java/pom.xml b/packages/sdk-java/pom.xml index 6e7fa921b..6a5fae4f4 100644 --- a/packages/sdk-java/pom.xml +++ b/packages/sdk-java/pom.xml @@ -34,7 +34,7 @@ 2.0.60 3.13.0 0.8.0 - 2 + 2.2.1 2.9.1 1.5 @@ -122,7 +122,7 @@ org.apache.maven.plugins maven-source-plugin - ${maven-source-plugin.version}.2.1 + ${maven-source-plugin.version} attach-sources From d7d7bf0c3980421f079f88093b9efee71f1316bd Mon Sep 17 00:00:00 2001 From: tanzhenxin Date: Tue, 6 Jan 2026 19:39:28 +0800 Subject: [PATCH 58/65] fix default values of reasoning config for openai compatible api --- .../core/openaiContentGenerator/pipeline.ts | 21 ++++++++++++------- .../provider/default.ts | 4 +--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/core/src/core/openaiContentGenerator/pipeline.ts b/packages/core/src/core/openaiContentGenerator/pipeline.ts index 88ac38f6a..ef27a7798 100644 --- a/packages/core/src/core/openaiContentGenerator/pipeline.ts +++ b/packages/core/src/core/openaiContentGenerator/pipeline.ts @@ -317,15 +317,22 @@ export class ContentGenerationPipeline { } private buildReasoningConfig(): Record { - const reasoning = this.contentGeneratorConfig.reasoning; + // Reasoning configuration for OpenAI-compatible endpoints is highly fragmented. + // For example, across common providers and models: + // + // - deepseek-reasoner — thinking is enabled by default and cannot be disabled + // - glm-4.7 — thinking is enabled by default; can be disabled via `extra_body.thinking.enabled` + // - kimi-k2-thinking — thinking is enabled by default and cannot be disabled + // - gpt-5.x series — thinking is enabled by default; can be disabled via `reasoning.effort` + // - qwen3 series — model-dependent; can be manually disabled via `extra_body.enable_thinking` + // + // Given this inconsistency, we choose not to set any reasoning config here and + // instead rely on each model’s default behavior. - if (reasoning === false) { - return {}; - } + // We plan to introduce provider- and model-specific settings to enable more + // fine-grained control over reasoning configuration. - return { - reasoning_effort: reasoning?.effort ?? 'medium', - }; + return {}; } /** diff --git a/packages/core/src/core/openaiContentGenerator/provider/default.ts b/packages/core/src/core/openaiContentGenerator/provider/default.ts index c56069503..521a6768c 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/default.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/default.ts @@ -58,8 +58,6 @@ export class DefaultOpenAICompatibleProvider } getDefaultGenerationConfig(): GenerateContentConfig { - return { - topP: 0.95, - }; + return {}; } } From 8fcdd86b9103dfe110944a8be7e0b57e1c418120 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Thu, 1 Jan 2026 20:17:15 +0800 Subject: [PATCH 59/65] feat(cli): add direct argument support for /approval-mode command Allow users to set approval mode directly via argument instead of opening the dialog. For example: - /approval-mode plan - /approval-mode yolo - /approval-mode auto-edit - /approval-mode default If no argument is provided, the dialog opens as before. If an invalid argument is provided, an error message shows valid options. Also adds tab completion for mode arguments. Fixes #1353 --- packages/cli/src/i18n/locales/en.js | 3 + packages/cli/src/i18n/locales/ru.js | 4 + packages/cli/src/i18n/locales/zh.js | 3 + .../ui/commands/approvalModeCommand.test.ts | 179 +++++++++++++++++- .../src/ui/commands/approvalModeCommand.ts | 71 ++++++- 5 files changed, 246 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js index 25fe74ece..5e8b16629 100644 --- a/packages/cli/src/i18n/locales/en.js +++ b/packages/cli/src/i18n/locales/en.js @@ -89,6 +89,9 @@ export default { 'No tools available': 'No tools available', 'View or change the approval mode for tool usage': 'View or change the approval mode for tool usage', + 'Invalid approval mode "{{arg}}". Valid modes: {{modes}}': + 'Invalid approval mode "{{arg}}". Valid modes: {{modes}}', + 'Approval mode set to "{{mode}}"': 'Approval mode set to "{{mode}}"', 'View or change the language setting': 'View or change the language setting', 'change the theme': 'change the theme', 'Select Theme': 'Select Theme', diff --git a/packages/cli/src/i18n/locales/ru.js b/packages/cli/src/i18n/locales/ru.js index 8db55e331..9685c104b 100644 --- a/packages/cli/src/i18n/locales/ru.js +++ b/packages/cli/src/i18n/locales/ru.js @@ -89,6 +89,10 @@ export default { 'No tools available': 'Нет доступных инструментов', 'View or change the approval mode for tool usage': 'Просмотр или изменение режима подтверждения для использования инструментов', + 'Invalid approval mode "{{arg}}". Valid modes: {{modes}}': + 'Недопустимый режим подтверждения "{{arg}}". Допустимые режимы: {{modes}}', + 'Approval mode set to "{{mode}}"': + 'Режим подтверждения установлен на "{{mode}}"', 'View or change the language setting': 'Просмотр или изменение настроек языка', 'change the theme': 'Изменение темы', diff --git a/packages/cli/src/i18n/locales/zh.js b/packages/cli/src/i18n/locales/zh.js index 5c5d21679..c3550f7e8 100644 --- a/packages/cli/src/i18n/locales/zh.js +++ b/packages/cli/src/i18n/locales/zh.js @@ -88,6 +88,9 @@ export default { 'No tools available': '没有可用工具', 'View or change the approval mode for tool usage': '查看或更改工具使用的审批模式', + 'Invalid approval mode "{{arg}}". Valid modes: {{modes}}': + '无效的审批模式 "{{arg}}"。有效模式:{{modes}}', + 'Approval mode set to "{{mode}}"': '审批模式已设置为 "{{mode}}"', 'View or change the language setting': '查看或更改语言设置', 'change the theme': '更改主题', 'Select Theme': '选择主题', diff --git a/packages/cli/src/ui/commands/approvalModeCommand.test.ts b/packages/cli/src/ui/commands/approvalModeCommand.test.ts index f915a63c9..7b4b4cfa4 100644 --- a/packages/cli/src/ui/commands/approvalModeCommand.test.ts +++ b/packages/cli/src/ui/commands/approvalModeCommand.test.ts @@ -4,29 +4,34 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; import { approvalModeCommand } from './approvalModeCommand.js'; import { type CommandContext, CommandKind, type OpenDialogActionReturn, + type MessageActionReturn, } from './types.js'; import { createMockCommandContext } from '../../test-utils/mockCommandContext.js'; import type { LoadedSettings } from '../../config/settings.js'; describe('approvalModeCommand', () => { let mockContext: CommandContext; + let mockSetValue: ReturnType; + let mockSetApprovalMode: ReturnType; beforeEach(() => { + mockSetValue = vi.fn(); + mockSetApprovalMode = vi.fn(); mockContext = createMockCommandContext({ services: { config: { getApprovalMode: () => 'default', - setApprovalMode: () => {}, + setApprovalMode: mockSetApprovalMode, }, settings: { - merged: {}, - setValue: () => {}, + merged: { tools: { approvalMode: 'default' } }, + setValue: mockSetValue, forScope: () => ({}), } as unknown as LoadedSettings, }, @@ -41,7 +46,7 @@ describe('approvalModeCommand', () => { expect(approvalModeCommand.kind).toBe(CommandKind.BUILT_IN); }); - it('should open approval mode dialog when invoked', async () => { + it('should open approval mode dialog when invoked without arguments', async () => { const result = (await approvalModeCommand.action?.( mockContext, '', @@ -51,21 +56,177 @@ describe('approvalModeCommand', () => { expect(result.dialog).toBe('approval-mode'); }); - it('should open approval mode dialog with arguments (ignored)', async () => { + it('should open approval mode dialog when invoked with whitespace only', async () => { const result = (await approvalModeCommand.action?.( mockContext, - 'some arguments', + ' ', )) as OpenDialogActionReturn; expect(result.type).toBe('dialog'); expect(result.dialog).toBe('approval-mode'); }); + describe('direct mode setting', () => { + it('should set approval mode to "plan" when argument is "plan"', async () => { + const result = (await approvalModeCommand.action?.( + mockContext, + 'plan', + )) as MessageActionReturn; + + expect(result.type).toBe('message'); + expect(result.messageType).toBe('info'); + expect(result.content).toContain('plan'); + expect(mockSetValue).toHaveBeenCalledWith( + 'User', + 'tools.approvalMode', + 'plan', + ); + expect(mockSetApprovalMode).toHaveBeenCalled(); + }); + + it('should set approval mode to "yolo" when argument is "yolo"', async () => { + const result = (await approvalModeCommand.action?.( + mockContext, + 'yolo', + )) as MessageActionReturn; + + expect(result.type).toBe('message'); + expect(result.messageType).toBe('info'); + expect(result.content).toContain('yolo'); + expect(mockSetValue).toHaveBeenCalledWith( + 'User', + 'tools.approvalMode', + 'yolo', + ); + }); + + it('should set approval mode to "auto-edit" when argument is "auto-edit"', async () => { + const result = (await approvalModeCommand.action?.( + mockContext, + 'auto-edit', + )) as MessageActionReturn; + + expect(result.type).toBe('message'); + expect(result.messageType).toBe('info'); + expect(result.content).toContain('auto-edit'); + expect(mockSetValue).toHaveBeenCalledWith( + 'User', + 'tools.approvalMode', + 'auto-edit', + ); + }); + + it('should set approval mode to "default" when argument is "default"', async () => { + const result = (await approvalModeCommand.action?.( + mockContext, + 'default', + )) as MessageActionReturn; + + expect(result.type).toBe('message'); + expect(result.messageType).toBe('info'); + expect(result.content).toContain('default'); + expect(mockSetValue).toHaveBeenCalledWith( + 'User', + 'tools.approvalMode', + 'default', + ); + }); + + it('should be case-insensitive for mode argument', async () => { + const result = (await approvalModeCommand.action?.( + mockContext, + 'YOLO', + )) as MessageActionReturn; + + expect(result.type).toBe('message'); + expect(result.messageType).toBe('info'); + expect(mockSetValue).toHaveBeenCalledWith( + 'User', + 'tools.approvalMode', + 'yolo', + ); + }); + + it('should handle argument with leading/trailing whitespace', async () => { + const result = (await approvalModeCommand.action?.( + mockContext, + ' plan ', + )) as MessageActionReturn; + + expect(result.type).toBe('message'); + expect(result.messageType).toBe('info'); + expect(mockSetValue).toHaveBeenCalledWith( + 'User', + 'tools.approvalMode', + 'plan', + ); + }); + }); + + describe('invalid mode argument', () => { + it('should return error for invalid mode', async () => { + const result = (await approvalModeCommand.action?.( + mockContext, + 'invalid-mode', + )) as MessageActionReturn; + + expect(result.type).toBe('message'); + expect(result.messageType).toBe('error'); + expect(result.content).toContain('invalid-mode'); + expect(result.content).toContain('plan'); + expect(result.content).toContain('yolo'); + expect(mockSetValue).not.toHaveBeenCalled(); + expect(mockSetApprovalMode).not.toHaveBeenCalled(); + }); + }); + it('should not have subcommands', () => { expect(approvalModeCommand.subCommands).toBeUndefined(); }); - it('should not have completion function', () => { - expect(approvalModeCommand.completion).toBeUndefined(); + describe('completion', () => { + it('should have completion function', () => { + expect(approvalModeCommand.completion).toBeDefined(); + }); + + it('should return all modes when partial arg is empty', async () => { + const completions = await approvalModeCommand.completion?.( + mockContext, + '', + ); + + expect(completions).toContain('plan'); + expect(completions).toContain('default'); + expect(completions).toContain('auto-edit'); + expect(completions).toContain('yolo'); + }); + + it('should filter modes based on partial arg', async () => { + const completions = await approvalModeCommand.completion?.( + mockContext, + 'p', + ); + + expect(completions).toContain('plan'); + expect(completions).not.toContain('yolo'); + }); + + it('should filter modes case-insensitively', async () => { + const completions = await approvalModeCommand.completion?.( + mockContext, + 'A', + ); + + expect(completions).toContain('auto-edit'); + }); + + it('should return empty array when no modes match', async () => { + const completions = await approvalModeCommand.completion?.( + mockContext, + 'xyz', + ); + + expect(completions).toEqual([]); + }); }); }); diff --git a/packages/cli/src/ui/commands/approvalModeCommand.ts b/packages/cli/src/ui/commands/approvalModeCommand.ts index 90ae774bf..b2f58bbf7 100644 --- a/packages/cli/src/ui/commands/approvalModeCommand.ts +++ b/packages/cli/src/ui/commands/approvalModeCommand.ts @@ -8,9 +8,26 @@ import type { SlashCommand, CommandContext, OpenDialogActionReturn, + MessageActionReturn, } from './types.js'; import { CommandKind } from './types.js'; import { t } from '../../i18n/index.js'; +import type { ApprovalMode} from '@qwen-code/qwen-code-core'; +import { APPROVAL_MODES } from '@qwen-code/qwen-code-core'; +import { SettingScope } from '../../config/settings.js'; + +/** + * Parses the argument string and returns the corresponding ApprovalMode if valid. + * Returns undefined if the argument is empty or not a valid mode. + */ +function parseApprovalModeArg(arg: string): ApprovalMode | undefined { + const trimmed = arg.trim().toLowerCase(); + if (!trimmed) { + return undefined; + } + // Match against valid approval modes (case-insensitive) + return APPROVAL_MODES.find((mode) => mode.toLowerCase() === trimmed); +} export const approvalModeCommand: SlashCommand = { name: 'approval-mode', @@ -19,10 +36,54 @@ export const approvalModeCommand: SlashCommand = { }, kind: CommandKind.BUILT_IN, action: async ( + context: CommandContext, + args: string, + ): Promise => { + const mode = parseApprovalModeArg(args); + + // If no argument provided, open the dialog + if (!args.trim()) { + return { + type: 'dialog', + dialog: 'approval-mode', + }; + } + + // If invalid argument, return error message with valid options + if (!mode) { + return { + type: 'message', + messageType: 'error', + content: t('Invalid approval mode "{{arg}}". Valid modes: {{modes}}', { + arg: args.trim(), + modes: APPROVAL_MODES.join(', '), + }), + }; + } + + // Set the mode directly + const { config, settings } = context.services; + if (config && settings) { + settings.setValue(SettingScope.User, 'tools.approvalMode', mode); + config.setApprovalMode(settings.merged.tools?.approvalMode ?? mode); + } + + return { + type: 'message', + messageType: 'info', + content: t('Approval mode set to "{{mode}}"', { mode }), + }; + }, + completion: async ( _context: CommandContext, - _args: string, - ): Promise => ({ - type: 'dialog', - dialog: 'approval-mode', - }), + partialArg: string, + ): Promise => { + const trimmed = partialArg.trim().toLowerCase(); + if (!trimmed) { + return [...APPROVAL_MODES]; + } + return APPROVAL_MODES.filter((mode) => + mode.toLowerCase().startsWith(trimmed), + ); + }, }; From 2f2937aafe1d95129a73eace54aea0ec0937a26c Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Thu, 1 Jan 2026 20:29:38 +0800 Subject: [PATCH 60/65] test: add explicit assertions for setApprovalMode argument Verify the exact mode value passed to config.setApprovalMode to catch potential regressions in settings merge/update mechanism. --- .../cli/src/ui/commands/approvalModeCommand.test.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/ui/commands/approvalModeCommand.test.ts b/packages/cli/src/ui/commands/approvalModeCommand.test.ts index 7b4b4cfa4..14a250a17 100644 --- a/packages/cli/src/ui/commands/approvalModeCommand.test.ts +++ b/packages/cli/src/ui/commands/approvalModeCommand.test.ts @@ -30,7 +30,9 @@ describe('approvalModeCommand', () => { setApprovalMode: mockSetApprovalMode, }, settings: { - merged: { tools: { approvalMode: 'default' } }, + // Use empty merged so ?? fallback triggers, allowing us to verify + // the exact mode passed to setApprovalMode + merged: {}, setValue: mockSetValue, forScope: () => ({}), } as unknown as LoadedSettings, @@ -81,7 +83,7 @@ describe('approvalModeCommand', () => { 'tools.approvalMode', 'plan', ); - expect(mockSetApprovalMode).toHaveBeenCalled(); + expect(mockSetApprovalMode).toHaveBeenCalledWith('plan'); }); it('should set approval mode to "yolo" when argument is "yolo"', async () => { @@ -98,6 +100,7 @@ describe('approvalModeCommand', () => { 'tools.approvalMode', 'yolo', ); + expect(mockSetApprovalMode).toHaveBeenCalledWith('yolo'); }); it('should set approval mode to "auto-edit" when argument is "auto-edit"', async () => { @@ -114,6 +117,7 @@ describe('approvalModeCommand', () => { 'tools.approvalMode', 'auto-edit', ); + expect(mockSetApprovalMode).toHaveBeenCalledWith('auto-edit'); }); it('should set approval mode to "default" when argument is "default"', async () => { @@ -130,6 +134,7 @@ describe('approvalModeCommand', () => { 'tools.approvalMode', 'default', ); + expect(mockSetApprovalMode).toHaveBeenCalledWith('default'); }); it('should be case-insensitive for mode argument', async () => { @@ -145,6 +150,7 @@ describe('approvalModeCommand', () => { 'tools.approvalMode', 'yolo', ); + expect(mockSetApprovalMode).toHaveBeenCalledWith('yolo'); }); it('should handle argument with leading/trailing whitespace', async () => { @@ -160,6 +166,7 @@ describe('approvalModeCommand', () => { 'tools.approvalMode', 'plan', ); + expect(mockSetApprovalMode).toHaveBeenCalledWith('plan'); }); }); From bfe72988584512621dca51a562df728de6deb8f0 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Tue, 6 Jan 2026 21:57:21 +0800 Subject: [PATCH 61/65] refactor: apply session-only approval mode per review feedback - Remove persistence to user settings (no setValue call) - Only use config.setApprovalMode() for session scope - Remove autocomplete feature for simplicity - Align with Shift+Tab behavior --- .../ui/commands/approvalModeCommand.test.ts | 89 +------------------ .../src/ui/commands/approvalModeCommand.ts | 24 ++--- 2 files changed, 8 insertions(+), 105 deletions(-) diff --git a/packages/cli/src/ui/commands/approvalModeCommand.test.ts b/packages/cli/src/ui/commands/approvalModeCommand.test.ts index 14a250a17..2cbae6319 100644 --- a/packages/cli/src/ui/commands/approvalModeCommand.test.ts +++ b/packages/cli/src/ui/commands/approvalModeCommand.test.ts @@ -13,15 +13,12 @@ import { type MessageActionReturn, } from './types.js'; import { createMockCommandContext } from '../../test-utils/mockCommandContext.js'; -import type { LoadedSettings } from '../../config/settings.js'; describe('approvalModeCommand', () => { let mockContext: CommandContext; - let mockSetValue: ReturnType; let mockSetApprovalMode: ReturnType; beforeEach(() => { - mockSetValue = vi.fn(); mockSetApprovalMode = vi.fn(); mockContext = createMockCommandContext({ services: { @@ -29,13 +26,6 @@ describe('approvalModeCommand', () => { getApprovalMode: () => 'default', setApprovalMode: mockSetApprovalMode, }, - settings: { - // Use empty merged so ?? fallback triggers, allowing us to verify - // the exact mode passed to setApprovalMode - merged: {}, - setValue: mockSetValue, - forScope: () => ({}), - } as unknown as LoadedSettings, }, }); }); @@ -68,7 +58,7 @@ describe('approvalModeCommand', () => { expect(result.dialog).toBe('approval-mode'); }); - describe('direct mode setting', () => { + describe('direct mode setting (session-only)', () => { it('should set approval mode to "plan" when argument is "plan"', async () => { const result = (await approvalModeCommand.action?.( mockContext, @@ -78,11 +68,6 @@ describe('approvalModeCommand', () => { expect(result.type).toBe('message'); expect(result.messageType).toBe('info'); expect(result.content).toContain('plan'); - expect(mockSetValue).toHaveBeenCalledWith( - 'User', - 'tools.approvalMode', - 'plan', - ); expect(mockSetApprovalMode).toHaveBeenCalledWith('plan'); }); @@ -95,11 +80,6 @@ describe('approvalModeCommand', () => { expect(result.type).toBe('message'); expect(result.messageType).toBe('info'); expect(result.content).toContain('yolo'); - expect(mockSetValue).toHaveBeenCalledWith( - 'User', - 'tools.approvalMode', - 'yolo', - ); expect(mockSetApprovalMode).toHaveBeenCalledWith('yolo'); }); @@ -112,11 +92,6 @@ describe('approvalModeCommand', () => { expect(result.type).toBe('message'); expect(result.messageType).toBe('info'); expect(result.content).toContain('auto-edit'); - expect(mockSetValue).toHaveBeenCalledWith( - 'User', - 'tools.approvalMode', - 'auto-edit', - ); expect(mockSetApprovalMode).toHaveBeenCalledWith('auto-edit'); }); @@ -129,11 +104,6 @@ describe('approvalModeCommand', () => { expect(result.type).toBe('message'); expect(result.messageType).toBe('info'); expect(result.content).toContain('default'); - expect(mockSetValue).toHaveBeenCalledWith( - 'User', - 'tools.approvalMode', - 'default', - ); expect(mockSetApprovalMode).toHaveBeenCalledWith('default'); }); @@ -145,11 +115,6 @@ describe('approvalModeCommand', () => { expect(result.type).toBe('message'); expect(result.messageType).toBe('info'); - expect(mockSetValue).toHaveBeenCalledWith( - 'User', - 'tools.approvalMode', - 'yolo', - ); expect(mockSetApprovalMode).toHaveBeenCalledWith('yolo'); }); @@ -161,11 +126,6 @@ describe('approvalModeCommand', () => { expect(result.type).toBe('message'); expect(result.messageType).toBe('info'); - expect(mockSetValue).toHaveBeenCalledWith( - 'User', - 'tools.approvalMode', - 'plan', - ); expect(mockSetApprovalMode).toHaveBeenCalledWith('plan'); }); }); @@ -182,7 +142,6 @@ describe('approvalModeCommand', () => { expect(result.content).toContain('invalid-mode'); expect(result.content).toContain('plan'); expect(result.content).toContain('yolo'); - expect(mockSetValue).not.toHaveBeenCalled(); expect(mockSetApprovalMode).not.toHaveBeenCalled(); }); }); @@ -191,49 +150,7 @@ describe('approvalModeCommand', () => { expect(approvalModeCommand.subCommands).toBeUndefined(); }); - describe('completion', () => { - it('should have completion function', () => { - expect(approvalModeCommand.completion).toBeDefined(); - }); - - it('should return all modes when partial arg is empty', async () => { - const completions = await approvalModeCommand.completion?.( - mockContext, - '', - ); - - expect(completions).toContain('plan'); - expect(completions).toContain('default'); - expect(completions).toContain('auto-edit'); - expect(completions).toContain('yolo'); - }); - - it('should filter modes based on partial arg', async () => { - const completions = await approvalModeCommand.completion?.( - mockContext, - 'p', - ); - - expect(completions).toContain('plan'); - expect(completions).not.toContain('yolo'); - }); - - it('should filter modes case-insensitively', async () => { - const completions = await approvalModeCommand.completion?.( - mockContext, - 'A', - ); - - expect(completions).toContain('auto-edit'); - }); - - it('should return empty array when no modes match', async () => { - const completions = await approvalModeCommand.completion?.( - mockContext, - 'xyz', - ); - - expect(completions).toEqual([]); - }); + it('should not have completion function', () => { + expect(approvalModeCommand.completion).toBeUndefined(); }); }); diff --git a/packages/cli/src/ui/commands/approvalModeCommand.ts b/packages/cli/src/ui/commands/approvalModeCommand.ts index b2f58bbf7..c53a4184d 100644 --- a/packages/cli/src/ui/commands/approvalModeCommand.ts +++ b/packages/cli/src/ui/commands/approvalModeCommand.ts @@ -12,9 +12,8 @@ import type { } from './types.js'; import { CommandKind } from './types.js'; import { t } from '../../i18n/index.js'; -import type { ApprovalMode} from '@qwen-code/qwen-code-core'; +import type { ApprovalMode } from '@qwen-code/qwen-code-core'; import { APPROVAL_MODES } from '@qwen-code/qwen-code-core'; -import { SettingScope } from '../../config/settings.js'; /** * Parses the argument string and returns the corresponding ApprovalMode if valid. @@ -61,11 +60,10 @@ export const approvalModeCommand: SlashCommand = { }; } - // Set the mode directly - const { config, settings } = context.services; - if (config && settings) { - settings.setValue(SettingScope.User, 'tools.approvalMode', mode); - config.setApprovalMode(settings.merged.tools?.approvalMode ?? mode); + // Set the mode for current session only (not persisted) + const { config } = context.services; + if (config) { + config.setApprovalMode(mode); } return { @@ -74,16 +72,4 @@ export const approvalModeCommand: SlashCommand = { content: t('Approval mode set to "{{mode}}"', { mode }), }; }, - completion: async ( - _context: CommandContext, - partialArg: string, - ): Promise => { - const trimmed = partialArg.trim().toLowerCase(); - if (!trimmed) { - return [...APPROVAL_MODES]; - } - return APPROVAL_MODES.filter((mode) => - mode.toLowerCase().startsWith(trimmed), - ); - }, }; From 0878ee4cbd0acb5e41a05f9ae24c0f3701911bb2 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Tue, 6 Jan 2026 22:04:43 +0800 Subject: [PATCH 62/65] fix: handle setApprovalMode error in untrusted folders Add try/catch to gracefully handle errors when setting privileged modes (yolo/auto-edit) in untrusted folders, returning an error message instead of throwing. --- .../ui/commands/approvalModeCommand.test.ts | 19 +++++++++++++++++++ .../src/ui/commands/approvalModeCommand.ts | 10 +++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/ui/commands/approvalModeCommand.test.ts b/packages/cli/src/ui/commands/approvalModeCommand.test.ts index 2cbae6319..c036bceda 100644 --- a/packages/cli/src/ui/commands/approvalModeCommand.test.ts +++ b/packages/cli/src/ui/commands/approvalModeCommand.test.ts @@ -146,6 +146,25 @@ describe('approvalModeCommand', () => { }); }); + describe('untrusted folder handling', () => { + it('should return error when setApprovalMode throws (e.g., untrusted folder)', async () => { + const errorMessage = + 'Cannot enable privileged approval modes in an untrusted folder.'; + mockSetApprovalMode.mockImplementation(() => { + throw new Error(errorMessage); + }); + + const result = (await approvalModeCommand.action?.( + mockContext, + 'yolo', + )) as MessageActionReturn; + + expect(result.type).toBe('message'); + expect(result.messageType).toBe('error'); + expect(result.content).toBe(errorMessage); + }); + }); + it('should not have subcommands', () => { expect(approvalModeCommand.subCommands).toBeUndefined(); }); diff --git a/packages/cli/src/ui/commands/approvalModeCommand.ts b/packages/cli/src/ui/commands/approvalModeCommand.ts index c53a4184d..f41e4b1cf 100644 --- a/packages/cli/src/ui/commands/approvalModeCommand.ts +++ b/packages/cli/src/ui/commands/approvalModeCommand.ts @@ -63,7 +63,15 @@ export const approvalModeCommand: SlashCommand = { // Set the mode for current session only (not persisted) const { config } = context.services; if (config) { - config.setApprovalMode(mode); + try { + config.setApprovalMode(mode); + } catch (e) { + return { + type: 'message', + messageType: 'error', + content: (e as Error).message, + }; + } } return { From 870d207f185108e2a5af63a5a0bb2f823385837c Mon Sep 17 00:00:00 2001 From: Weaxs <459312872@qq.com> Date: Wed, 7 Jan 2026 10:25:34 +0800 Subject: [PATCH 63/65] revert enable_thinking & thinking_budget --- .../core/openaiContentGenerator/pipeline.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/core/src/core/openaiContentGenerator/pipeline.ts b/packages/core/src/core/openaiContentGenerator/pipeline.ts index ba483fe5f..88ac38f6a 100644 --- a/packages/core/src/core/openaiContentGenerator/pipeline.ts +++ b/packages/core/src/core/openaiContentGenerator/pipeline.ts @@ -242,25 +242,6 @@ export class ContentGenerationPipeline { baseRequest.stream_options = { include_usage: true }; } - // Add thinking options if present - if ( - request.config?.thinkingConfig && - request.config.thinkingConfig.includeThoughts - ) { - ( - baseRequest as OpenAI.Chat.ChatCompletionCreateParams & { - enable_thinking?: boolean; - } - ).enable_thinking = true; - if (request.config.thinkingConfig.thinkingBudget) { - ( - baseRequest as OpenAI.Chat.ChatCompletionCreateParams & { - thinking_budget?: number; - } - ).thinking_budget = request.config.thinkingConfig.thinkingBudget; - } - } - // Add tools if present if (request.config?.tools) { baseRequest.tools = await this.converter.convertGeminiToolsToOpenAI( From f2d941e4696e349b5a53150d468d6e9c74e64986 Mon Sep 17 00:00:00 2001 From: "mingholy.lmh" Date: Wed, 7 Jan 2026 16:25:14 +0800 Subject: [PATCH 64/65] chore: bump version to 0.6.1 --- package-lock.json | 12 ++++++------ package.json | 4 ++-- packages/cli/package.json | 4 ++-- packages/core/package.json | 2 +- packages/test-utils/package.json | 2 +- packages/vscode-ide-companion/package.json | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 330b90e08..0ed7071f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@qwen-code/qwen-code", - "version": "0.6.0", + "version": "0.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@qwen-code/qwen-code", - "version": "0.6.0", + "version": "0.6.1", "workspaces": [ "packages/*" ], @@ -17316,7 +17316,7 @@ }, "packages/cli": { "name": "@qwen-code/qwen-code", - "version": "0.6.0", + "version": "0.6.1", "dependencies": { "@google/genai": "1.30.0", "@iarna/toml": "^2.2.5", @@ -17953,7 +17953,7 @@ }, "packages/core": { "name": "@qwen-code/qwen-code-core", - "version": "0.6.0", + "version": "0.6.1", "hasInstallScript": true, "dependencies": { "@anthropic-ai/sdk": "^0.36.1", @@ -21413,7 +21413,7 @@ }, "packages/test-utils": { "name": "@qwen-code/qwen-code-test-utils", - "version": "0.6.0", + "version": "0.6.1", "dev": true, "license": "Apache-2.0", "devDependencies": { @@ -21425,7 +21425,7 @@ }, "packages/vscode-ide-companion": { "name": "qwen-code-vscode-ide-companion", - "version": "0.6.0", + "version": "0.6.1", "license": "LICENSE", "dependencies": { "@modelcontextprotocol/sdk": "^1.25.1", diff --git a/package.json b/package.json index c239067ff..107b9e9b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/qwen-code", - "version": "0.6.0", + "version": "0.6.1", "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.6.0" + "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.6.1" }, "scripts": { "start": "cross-env node scripts/start.js", diff --git a/packages/cli/package.json b/packages/cli/package.json index f2083fe19..2154e4683 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/qwen-code", - "version": "0.6.0", + "version": "0.6.1", "description": "Qwen Code", "repository": { "type": "git", @@ -33,7 +33,7 @@ "dist" ], "config": { - "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.6.0" + "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.6.1" }, "dependencies": { "@google/genai": "1.30.0", diff --git a/packages/core/package.json b/packages/core/package.json index de06c78bc..e7baa13b2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/qwen-code-core", - "version": "0.6.0", + "version": "0.6.1", "description": "Qwen Code Core", "repository": { "type": "git", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index a1310056f..435df48f3 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/qwen-code-test-utils", - "version": "0.6.0", + "version": "0.6.1", "private": true, "main": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/vscode-ide-companion/package.json b/packages/vscode-ide-companion/package.json index 5fa753162..50982df00 100644 --- a/packages/vscode-ide-companion/package.json +++ b/packages/vscode-ide-companion/package.json @@ -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.6.0", + "version": "0.6.1", "publisher": "qwenlm", "icon": "assets/icon.png", "repository": { From bfc3bbfa9c425db872f4543729b6d3a77eaaeabd Mon Sep 17 00:00:00 2001 From: tanzhenxin Date: Wed, 7 Jan 2026 17:25:27 +0800 Subject: [PATCH 65/65] update user messages --- packages/cli/src/ui/IdeIntegrationNudge.tsx | 2 +- packages/cli/src/ui/commands/ideCommand.ts | 16 ++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/ui/IdeIntegrationNudge.tsx b/packages/cli/src/ui/IdeIntegrationNudge.tsx index a53f59b98..9cd6d5311 100644 --- a/packages/cli/src/ui/IdeIntegrationNudge.tsx +++ b/packages/cli/src/ui/IdeIntegrationNudge.tsx @@ -74,7 +74,7 @@ export function IdeIntegrationNudge({ const installText = isInSandbox ? `Note: In sandbox environments, IDE integration requires manual setup on the host system. If you select Yes, you'll receive instructions on how to set this up.` : isExtensionPreInstalled - ? `The IDE extension appears to be already installed. If you select Yes, the CLI will connect to your ${ + ? `If you select Yes, the CLI will connect to your ${ ideName ?? 'editor' } and have access to your open files and display diffs directly.` : `If you select Yes, we'll install an extension that allows the CLI to access your open files and display diffs directly in ${ diff --git a/packages/cli/src/ui/commands/ideCommand.ts b/packages/cli/src/ui/commands/ideCommand.ts index 2440ca852..be3a12bc9 100644 --- a/packages/cli/src/ui/commands/ideCommand.ts +++ b/packages/cli/src/ui/commands/ideCommand.ts @@ -204,22 +204,10 @@ export const ideCommand = async (): Promise => { } if (!installer) { const ideName = ideClient.getDetectedIdeDisplayName(); - const isVSCode = currentIDE.name === 'vscode'; - let type: 'error' | 'info' = 'error'; - let message: string; - if (isVSCode) { - // VS Code - message = `No installer is available for ${ideName}. Please install the '${QWEN_CODE_COMPANION_EXTENSION_NAME}' extension manually from the marketplace.`; - } else { - // NO VS Code - type = 'info'; - message = `Automatic installation is not supported for ${ideName}. Please install the extension manually or install '${QWEN_CODE_COMPANION_EXTENSION_NAME}' in VS Code. If you have installed it before, please ignore the reminder and directly connect the ide extension`; - } - context.ui.addItem( { - type, - text: message, + type: 'error', + text: `Automatic installation is not supported for ${ideName}. Please install the '${QWEN_CODE_COMPANION_EXTENSION_NAME}' extension manually from the marketplace.`, }, Date.now(), );