mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-20 01:01:53 +00:00
refactor(acp): centralize error codes and add RESOURCE_NOT_FOUND handling for file operations
This commit is contained in:
parent
55a5df46ba
commit
bdfeec24fb
9 changed files with 151 additions and 40 deletions
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { z } from 'zod';
|
||||
import * as schema from './schema.js';
|
||||
import { ACP_ERROR_CODES } from './errorCodes.js';
|
||||
export * from './schema.js';
|
||||
|
||||
import type { WritableStream, ReadableStream } from 'node:stream/web';
|
||||
|
|
@ -349,27 +350,51 @@ export class RequestError extends Error {
|
|||
}
|
||||
|
||||
static parseError(details?: string): RequestError {
|
||||
return new RequestError(-32700, 'Parse error', details);
|
||||
return new RequestError(
|
||||
ACP_ERROR_CODES.PARSE_ERROR,
|
||||
'Parse error',
|
||||
details,
|
||||
);
|
||||
}
|
||||
|
||||
static invalidRequest(details?: string): RequestError {
|
||||
return new RequestError(-32600, 'Invalid request', details);
|
||||
return new RequestError(
|
||||
ACP_ERROR_CODES.INVALID_REQUEST,
|
||||
'Invalid request',
|
||||
details,
|
||||
);
|
||||
}
|
||||
|
||||
static methodNotFound(details?: string): RequestError {
|
||||
return new RequestError(-32601, 'Method not found', details);
|
||||
return new RequestError(
|
||||
ACP_ERROR_CODES.METHOD_NOT_FOUND,
|
||||
'Method not found',
|
||||
details,
|
||||
);
|
||||
}
|
||||
|
||||
static invalidParams(details?: string): RequestError {
|
||||
return new RequestError(-32602, 'Invalid params', details);
|
||||
return new RequestError(
|
||||
ACP_ERROR_CODES.INVALID_PARAMS,
|
||||
'Invalid params',
|
||||
details,
|
||||
);
|
||||
}
|
||||
|
||||
static internalError(details?: string): RequestError {
|
||||
return new RequestError(-32603, 'Internal error', details);
|
||||
return new RequestError(
|
||||
ACP_ERROR_CODES.INTERNAL_ERROR,
|
||||
'Internal error',
|
||||
details,
|
||||
);
|
||||
}
|
||||
|
||||
static authRequired(details?: string): RequestError {
|
||||
return new RequestError(-32000, 'Authentication required', details);
|
||||
return new RequestError(
|
||||
ACP_ERROR_CODES.AUTH_REQUIRED,
|
||||
'Authentication required',
|
||||
details,
|
||||
);
|
||||
}
|
||||
|
||||
toResult<T>(): Result<T> {
|
||||
|
|
|
|||
25
packages/cli/src/acp-integration/errorCodes.ts
Normal file
25
packages/cli/src/acp-integration/errorCodes.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export const ACP_ERROR_CODES = {
|
||||
// Parse error: invalid JSON received by server.
|
||||
PARSE_ERROR: -32700,
|
||||
// Invalid request: JSON is not a valid Request object.
|
||||
INVALID_REQUEST: -32600,
|
||||
// Method not found: method does not exist or is unavailable.
|
||||
METHOD_NOT_FOUND: -32601,
|
||||
// Invalid params: invalid method parameter(s).
|
||||
INVALID_PARAMS: -32602,
|
||||
// Internal error: implementation-defined server error.
|
||||
INTERNAL_ERROR: -32603,
|
||||
// Authentication required: must authenticate before operation.
|
||||
AUTH_REQUIRED: -32000,
|
||||
// Resource not found: e.g. missing file.
|
||||
RESOURCE_NOT_FOUND: -32002,
|
||||
} as const;
|
||||
|
||||
export type AcpErrorCode =
|
||||
(typeof ACP_ERROR_CODES)[keyof typeof ACP_ERROR_CODES];
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import type { FileSystemService } from '@qwen-code/qwen-code-core';
|
||||
import type * as acp from '../acp.js';
|
||||
import { ACP_ERROR_CODES } from '../errorCodes.js';
|
||||
|
||||
/**
|
||||
* ACP client-based implementation of FileSystemService
|
||||
|
|
@ -23,25 +24,31 @@ export class AcpFileSystemService implements FileSystemService {
|
|||
return this.fallback.readTextFile(filePath);
|
||||
}
|
||||
|
||||
const response = await this.client.readTextFile({
|
||||
path: filePath,
|
||||
sessionId: this.sessionId,
|
||||
line: null,
|
||||
limit: null,
|
||||
});
|
||||
let response: { content: string };
|
||||
try {
|
||||
response = await this.client.readTextFile({
|
||||
path: filePath,
|
||||
sessionId: this.sessionId,
|
||||
line: null,
|
||||
limit: null,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorCode =
|
||||
typeof error === 'object' && error !== null && 'code' in error
|
||||
? (error as { code?: unknown }).code
|
||||
: undefined;
|
||||
|
||||
if (response.content.startsWith('ERROR: ENOENT:')) {
|
||||
// Treat ACP error strings as structured ENOENT errors without
|
||||
// assuming a specific platform format.
|
||||
const match = /^ERROR:\s*ENOENT:\s*(?<path>.*)$/i.exec(response.content);
|
||||
const err = new Error(response.content) as NodeJS.ErrnoException;
|
||||
err.code = 'ENOENT';
|
||||
err.errno = -2;
|
||||
const rawPath = match?.groups?.['path']?.trim();
|
||||
err['path'] = rawPath
|
||||
? rawPath.replace(/^['"]|['"]$/g, '') || filePath
|
||||
: filePath;
|
||||
throw err;
|
||||
if (errorCode === ACP_ERROR_CODES.RESOURCE_NOT_FOUND) {
|
||||
const err = new Error(
|
||||
`File not found: ${filePath}`,
|
||||
) as NodeJS.ErrnoException;
|
||||
err.code = 'ENOENT';
|
||||
err.errno = -2;
|
||||
err.path = filePath;
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return response.content;
|
||||
|
|
|
|||
|
|
@ -23,3 +23,23 @@ export const CLIENT_METHODS = {
|
|||
session_request_permission: 'session/request_permission',
|
||||
session_update: 'session/update',
|
||||
} as const;
|
||||
|
||||
export const ACP_ERROR_CODES = {
|
||||
// Parse error: invalid JSON received by server.
|
||||
PARSE_ERROR: -32700,
|
||||
// Invalid request: JSON is not a valid Request object.
|
||||
INVALID_REQUEST: -32600,
|
||||
// Method not found: method does not exist or is unavailable.
|
||||
METHOD_NOT_FOUND: -32601,
|
||||
// Invalid params: invalid method parameter(s).
|
||||
INVALID_PARAMS: -32602,
|
||||
// Internal error: implementation-defined server error.
|
||||
INTERNAL_ERROR: -32603,
|
||||
// Authentication required: must authenticate before operation.
|
||||
AUTH_REQUIRED: -32000,
|
||||
// Resource not found: e.g. missing file.
|
||||
RESOURCE_NOT_FOUND: -32002,
|
||||
} as const;
|
||||
|
||||
export type AcpErrorCode =
|
||||
(typeof ACP_ERROR_CODES)[keyof typeof ACP_ERROR_CODES];
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import * as os from 'node:os';
|
|||
import type { z } from 'zod';
|
||||
import type { DiffManager } from './diff-manager.js';
|
||||
import { OpenFilesManager } from './open-files-manager.js';
|
||||
import { ACP_ERROR_CODES } from './constants/acpSchema.js';
|
||||
|
||||
class CORSError extends Error {
|
||||
constructor(message: string) {
|
||||
|
|
@ -264,7 +265,7 @@ export class IDEServer {
|
|||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32000,
|
||||
code: ACP_ERROR_CODES.AUTH_REQUIRED,
|
||||
message:
|
||||
'Bad Request: No valid session ID provided for non-initialize request.',
|
||||
},
|
||||
|
|
@ -283,7 +284,7 @@ export class IDEServer {
|
|||
res.status(500).json({
|
||||
jsonrpc: '2.0' as const,
|
||||
error: {
|
||||
code: -32603,
|
||||
code: ACP_ERROR_CODES.INTERNAL_ERROR,
|
||||
message: 'Internal server error',
|
||||
},
|
||||
id: null,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { JSONRPC_VERSION } from '../types/acpTypes.js';
|
||||
import { ACP_ERROR_CODES } from '../constants/acpSchema.js';
|
||||
import type {
|
||||
AcpMessage,
|
||||
AcpPermissionRequest,
|
||||
|
|
@ -232,12 +233,34 @@ export class AcpConnection {
|
|||
})
|
||||
.catch((error) => {
|
||||
if ('id' in message && typeof message.id === 'number') {
|
||||
const errorMessage =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: typeof error === 'object' &&
|
||||
error !== null &&
|
||||
'message' in error &&
|
||||
typeof (error as { message: unknown }).message === 'string'
|
||||
? (error as { message: string }).message
|
||||
: String(error);
|
||||
|
||||
let errorCode: number = ACP_ERROR_CODES.INTERNAL_ERROR;
|
||||
const errorCodeValue =
|
||||
typeof error === 'object' && error !== null && 'code' in error
|
||||
? (error as { code?: unknown }).code
|
||||
: undefined;
|
||||
|
||||
if (typeof errorCodeValue === 'number') {
|
||||
errorCode = errorCodeValue;
|
||||
} else if (errorCodeValue === 'ENOENT') {
|
||||
errorCode = ACP_ERROR_CODES.RESOURCE_NOT_FOUND;
|
||||
}
|
||||
|
||||
this.messageHandler.sendResponseMessage(this.child, {
|
||||
jsonrpc: JSONRPC_VERSION,
|
||||
id: message.id,
|
||||
error: {
|
||||
code: -32603,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
code: errorCode,
|
||||
message: errorMessage,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,11 @@ export class AcpFileHandler {
|
|||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
console.error(`[ACP] Failed to read file ${params.path}:`, errorMsg);
|
||||
|
||||
const nodeError = error as NodeJS.ErrnoException;
|
||||
if (nodeError?.code === 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new Error(`Failed to read file '${params.path}': ${errorMsg}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { ACP_ERROR_CODES } from '../constants/acpSchema.js';
|
||||
|
||||
const AUTH_ERROR_PATTERNS = [
|
||||
'Authentication required', // Standard authentication request message
|
||||
'(code: -32000)', // RPC error code -32000 indicates authentication failure
|
||||
`(code: ${ACP_ERROR_CODES.AUTH_REQUIRED})`, // RPC error code indicates auth failure
|
||||
'Unauthorized', // HTTP unauthorized error
|
||||
'Invalid token', // Invalid token
|
||||
'Session expired', // Session expired
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import * as vscode from 'vscode';
|
|||
import { BaseMessageHandler } from './BaseMessageHandler.js';
|
||||
import type { ChatMessage } from '../../services/qwenAgentManager.js';
|
||||
import type { ApprovalModeValue } from '../../types/approvalModeValueTypes.js';
|
||||
import { ACP_ERROR_CODES } from '../../constants/acpSchema.js';
|
||||
|
||||
const AUTH_REQUIRED_CODE_PATTERN = `(code: ${ACP_ERROR_CODES.AUTH_REQUIRED})`;
|
||||
|
||||
/**
|
||||
* Session message handler
|
||||
|
|
@ -355,7 +358,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
createErr instanceof Error ? createErr.message : String(createErr);
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)')
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN)
|
||||
) {
|
||||
await this.promptLogin(
|
||||
'Your login session has expired or is invalid. Please login again to continue using Qwen Code.',
|
||||
|
|
@ -421,7 +424,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
errorMsg.includes('Session not found') ||
|
||||
errorMsg.includes('No active ACP session') ||
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)') ||
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
errorMsg.includes('Unauthorized') ||
|
||||
errorMsg.includes('Invalid token')
|
||||
) {
|
||||
|
|
@ -512,7 +515,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)') ||
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
errorMsg.includes('Unauthorized') ||
|
||||
errorMsg.includes('Invalid token') ||
|
||||
errorMsg.includes('No active ACP session')
|
||||
|
|
@ -622,7 +625,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)') ||
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
errorMsg.includes('Unauthorized') ||
|
||||
errorMsg.includes('Invalid token') ||
|
||||
errorMsg.includes('No active ACP session')
|
||||
|
|
@ -682,7 +685,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
// Check for authentication/session expiration errors in session creation
|
||||
if (
|
||||
createErrorMsg.includes('Authentication required') ||
|
||||
createErrorMsg.includes('(code: -32000)') ||
|
||||
createErrorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
createErrorMsg.includes('Unauthorized') ||
|
||||
createErrorMsg.includes('Invalid token') ||
|
||||
createErrorMsg.includes('No active ACP session')
|
||||
|
|
@ -722,7 +725,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)') ||
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
errorMsg.includes('Unauthorized') ||
|
||||
errorMsg.includes('Invalid token') ||
|
||||
errorMsg.includes('No active ACP session')
|
||||
|
|
@ -777,7 +780,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)') ||
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
errorMsg.includes('Unauthorized') ||
|
||||
errorMsg.includes('Invalid token') ||
|
||||
errorMsg.includes('No active ACP session')
|
||||
|
|
@ -827,7 +830,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)') ||
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
errorMsg.includes('Unauthorized') ||
|
||||
errorMsg.includes('Invalid token') ||
|
||||
errorMsg.includes('No active ACP session')
|
||||
|
|
@ -855,7 +858,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)') ||
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
errorMsg.includes('Unauthorized') ||
|
||||
errorMsg.includes('Invalid token') ||
|
||||
errorMsg.includes('No active ACP session')
|
||||
|
|
@ -961,7 +964,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)') ||
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
errorMsg.includes('Unauthorized') ||
|
||||
errorMsg.includes('Invalid token') ||
|
||||
errorMsg.includes('No active ACP session')
|
||||
|
|
@ -989,7 +992,7 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
// Check for authentication/session expiration errors
|
||||
if (
|
||||
errorMsg.includes('Authentication required') ||
|
||||
errorMsg.includes('(code: -32000)') ||
|
||||
errorMsg.includes(AUTH_REQUIRED_CODE_PATTERN) ||
|
||||
errorMsg.includes('Unauthorized') ||
|
||||
errorMsg.includes('Invalid token') ||
|
||||
errorMsg.includes('No active ACP session')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue