mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 12:11:09 +00:00
fix(core): handle ENOENT from ACP readTextFileWithInfo gracefully
- Add readTextFileWithInfo implementation in AcpFileSystemService - Convert RESOURCE_NOT_FOUND errors to ENOENT for consistent handling - Strip UTF-8 BOM from ACP responses - Treat ENOENT errors in write-file tool as new file creation Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
bddce08750
commit
2cbd092835
5 changed files with 213 additions and 41 deletions
|
|
@ -179,4 +179,83 @@ describe('AcpFileSystemService', () => {
|
|||
expect(client.readTextFile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('readTextFileWithInfo', () => {
|
||||
it('reads through ACP and strips UTF-8 BOM', async () => {
|
||||
const client = {
|
||||
readTextFile: vi.fn().mockResolvedValue({ content: '\ufeffhello' }),
|
||||
} as unknown as AgentSideConnection;
|
||||
|
||||
const svc = new AcpFileSystemService(
|
||||
client,
|
||||
'session-info-1',
|
||||
{ readTextFile: true, writeTextFile: true },
|
||||
createFallback(),
|
||||
);
|
||||
|
||||
const result = await svc.readTextFileWithInfo('/some/file.txt');
|
||||
expect(result).toEqual({
|
||||
content: 'hello',
|
||||
encoding: 'utf-8',
|
||||
bom: true,
|
||||
});
|
||||
expect(client.readTextFile).toHaveBeenCalledWith({
|
||||
path: '/some/file.txt',
|
||||
sessionId: 'session-info-1',
|
||||
});
|
||||
});
|
||||
|
||||
it('converts RESOURCE_NOT_FOUND error to ENOENT', async () => {
|
||||
const resourceNotFoundError = {
|
||||
code: RESOURCE_NOT_FOUND_CODE,
|
||||
message: 'File not found',
|
||||
};
|
||||
const client = {
|
||||
readTextFile: vi.fn().mockRejectedValue(resourceNotFoundError),
|
||||
} as unknown as AgentSideConnection;
|
||||
|
||||
const svc = new AcpFileSystemService(
|
||||
client,
|
||||
'session-info-2',
|
||||
{ readTextFile: true, writeTextFile: true },
|
||||
createFallback(),
|
||||
);
|
||||
|
||||
await expect(
|
||||
svc.readTextFileWithInfo('/some/missing.txt'),
|
||||
).rejects.toMatchObject({
|
||||
code: 'ENOENT',
|
||||
errno: -2,
|
||||
path: '/some/missing.txt',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses fallback when readTextFile capability is disabled', async () => {
|
||||
const client = {
|
||||
readTextFile: vi.fn(),
|
||||
} as unknown as AgentSideConnection;
|
||||
const fallback = createFallback();
|
||||
(
|
||||
fallback.readTextFileWithInfo as ReturnType<typeof vi.fn>
|
||||
).mockResolvedValue({ content: 'fallback', encoding: 'gbk', bom: false });
|
||||
|
||||
const svc = new AcpFileSystemService(
|
||||
client,
|
||||
'session-info-3',
|
||||
{ readTextFile: false, writeTextFile: true },
|
||||
fallback,
|
||||
);
|
||||
|
||||
const result = await svc.readTextFileWithInfo('/some/file.txt');
|
||||
expect(result).toEqual({
|
||||
content: 'fallback',
|
||||
encoding: 'gbk',
|
||||
bom: false,
|
||||
});
|
||||
expect(fallback.readTextFileWithInfo).toHaveBeenCalledWith(
|
||||
'/some/file.txt',
|
||||
);
|
||||
expect(client.readTextFile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,6 +16,26 @@ import type {
|
|||
|
||||
const RESOURCE_NOT_FOUND_CODE = -32002;
|
||||
|
||||
function getErrorCode(error: unknown): unknown {
|
||||
if (error instanceof RequestError) {
|
||||
return error.code;
|
||||
}
|
||||
|
||||
if (typeof error === 'object' && error !== null && 'code' in error) {
|
||||
return (error as { code?: unknown }).code;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function createEnoentError(filePath: string): NodeJS.ErrnoException {
|
||||
const err = new Error(`File not found: ${filePath}`) as NodeJS.ErrnoException;
|
||||
err.code = 'ENOENT';
|
||||
err.errno = -2;
|
||||
err.path = filePath;
|
||||
return err;
|
||||
}
|
||||
|
||||
export class AcpFileSystemService implements FileSystemService {
|
||||
constructor(
|
||||
private readonly connection: AgentSideConnection,
|
||||
|
|
@ -36,21 +56,10 @@ export class AcpFileSystemService implements FileSystemService {
|
|||
sessionId: this.sessionId,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorCode =
|
||||
error instanceof RequestError
|
||||
? error.code
|
||||
: typeof error === 'object' && error !== null && 'code' in error
|
||||
? (error as { code?: unknown }).code
|
||||
: undefined;
|
||||
const errorCode = getErrorCode(error);
|
||||
|
||||
if (errorCode === RESOURCE_NOT_FOUND_CODE) {
|
||||
const err = new Error(
|
||||
`File not found: ${filePath}`,
|
||||
) as NodeJS.ErrnoException;
|
||||
err.code = 'ENOENT';
|
||||
err.errno = -2;
|
||||
err.path = filePath;
|
||||
throw err;
|
||||
throw createEnoentError(filePath);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
|
@ -60,9 +69,33 @@ export class AcpFileSystemService implements FileSystemService {
|
|||
}
|
||||
|
||||
async readTextFileWithInfo(filePath: string): Promise<FileReadResult> {
|
||||
// ACP protocol does not expose encoding metadata; delegate to the local
|
||||
// fallback which performs a single-pass read with encoding detection.
|
||||
return this.fallback.readTextFileWithInfo(filePath);
|
||||
if (!this.capabilities.readTextFile) {
|
||||
return this.fallback.readTextFileWithInfo(filePath);
|
||||
}
|
||||
|
||||
let response: { content: string };
|
||||
try {
|
||||
response = await this.connection.readTextFile({
|
||||
path: filePath,
|
||||
sessionId: this.sessionId,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorCode = getErrorCode(error);
|
||||
if (errorCode === RESOURCE_NOT_FOUND_CODE) {
|
||||
throw createEnoentError(filePath);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const hasUtf8Bom =
|
||||
response.content.length > 0 && response.content.codePointAt(0) === 0xfeff;
|
||||
|
||||
return {
|
||||
content: hasUtf8Bom ? response.content.slice(1) : response.content,
|
||||
// ACP protocol currently returns text only and does not expose source encoding.
|
||||
encoding: 'utf-8',
|
||||
bom: hasUtf8Bom,
|
||||
};
|
||||
}
|
||||
|
||||
async writeTextFile(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue