fix(acp): add authMethods in set_model response errors

This commit is contained in:
mingholy.lmh 2026-02-01 19:47:15 +08:00
parent 6fa11a7bae
commit 06b37bd6bf
7 changed files with 166 additions and 24 deletions

View file

@ -9,6 +9,7 @@
import { z } from 'zod';
import * as schema from './schema.js';
import { ACP_ERROR_CODES } from './errorCodes.js';
import { pickAuthMethodsForDetails } from './authMethods.js';
export * from './schema.js';
import type { WritableStream, ReadableStream } from 'node:stream/web';
@ -180,6 +181,7 @@ type ErrorResponse = {
code: number;
message: string;
data?: unknown;
authMethods?: schema.AuthMethod[];
};
type PendingResponse = {
@ -282,8 +284,11 @@ class Connection {
details = error.message;
}
if (errorName === 'TokenManagerError') {
return RequestError.authRequired(details).toResult();
if (errorName === 'TokenManagerError' || details?.includes('/auth')) {
return RequestError.authRequired(
details,
pickAuthMethodsForDetails(details),
).toResult();
}
if (details?.includes('/auth')) {
@ -339,17 +344,24 @@ class Connection {
}
export class RequestError extends Error {
data?: { details?: string };
data?: { details?: string; authMethods?: schema.AuthMethod[] };
constructor(
public code: number,
message: string,
details?: string,
authMethods?: schema.AuthMethod[],
) {
super(message);
this.name = 'RequestError';
if (details) {
this.data = { details };
if (details || authMethods) {
this.data = {};
if (details) {
this.data.details = details;
}
if (authMethods) {
this.data.authMethods = authMethods;
}
}
}
@ -393,11 +405,15 @@ export class RequestError extends Error {
);
}
static authRequired(details?: string): RequestError {
static authRequired(
details?: string,
authMethods?: schema.AuthMethod[],
): RequestError {
return new RequestError(
ACP_ERROR_CODES.AUTH_REQUIRED,
'Authentication required',
details,
authMethods,
);
}

View file

@ -22,6 +22,7 @@ import {
} from '@qwen-code/qwen-code-core';
import type { ApprovalModeValue } from './schema.js';
import * as acp from './acp.js';
import { buildAuthMethods } from './authMethods.js';
import { AcpFileSystemService } from './service/filesystem.js';
import { Readable, Writable } from 'node:stream';
import type { LoadedSettings } from '../config/settings.js';
@ -73,20 +74,7 @@ class GeminiAgent {
args: acp.InitializeRequest,
): Promise<acp.InitializeResponse> {
this.clientCapabilities = args.clientCapabilities;
const authMethods = [
{
id: AuthType.USE_OPENAI,
name: 'Use OpenAI API key',
description:
'Requires setting the `OPENAI_API_KEY` environment variable',
},
{
id: AuthType.QWEN_OAUTH,
name: 'Qwen OAuth',
description:
'OAuth authentication for Qwen models with 2000 daily requests',
},
];
const authMethods = buildAuthMethods();
// Get current approval mode from config
const currentApprovalMode = this.config.getApprovalMode();
@ -290,7 +278,7 @@ class GeminiAgent {
`Session not found for id: ${params.sessionId}`,
);
}
return session.setModel(params);
return await session.setModel(params);
}
private async ensureAuthenticated(config: Config): Promise<void> {
@ -298,6 +286,7 @@ class GeminiAgent {
if (!selectedType) {
throw acp.RequestError.authRequired(
'Use Qwen Code CLI to authenticate first.',
this.pickAuthMethodsForAuthRequired(),
);
}
@ -308,10 +297,55 @@ class GeminiAgent {
console.error(`Authentication failed: ${e}`);
throw acp.RequestError.authRequired(
'Authentication failed: ' + (e as Error).message,
this.pickAuthMethodsForAuthRequired(selectedType, e),
);
}
}
private pickAuthMethodsForAuthRequired(
selectedType?: AuthType | string,
error?: unknown,
): acp.AuthMethod[] {
const authMethods = buildAuthMethods();
const errorMessage = this.extractErrorMessage(error);
if (
errorMessage?.includes('qwen-oauth') ||
errorMessage?.includes('Qwen OAuth')
) {
const qwenOAuthMethods = authMethods.filter(
(method) => method.id === AuthType.QWEN_OAUTH,
);
return qwenOAuthMethods.length ? qwenOAuthMethods : authMethods;
}
if (selectedType) {
const matchedMethods = authMethods.filter(
(method) => method.id === selectedType,
);
return matchedMethods.length ? matchedMethods : authMethods;
}
return authMethods;
}
private extractErrorMessage(error?: unknown): string | undefined {
if (error instanceof Error) {
return error.message;
}
if (
typeof error === 'object' &&
error != null &&
'message' in error &&
typeof error.message === 'string'
) {
return error.message;
}
if (typeof error === 'string') {
return error;
}
return undefined;
}
private setupFileSystem(config: Config): void {
if (!this.clientCapabilities?.fs) {
return;

View file

@ -0,0 +1,47 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { AuthType } from '@qwen-code/qwen-code-core';
import type { AuthMethod } from './schema.js';
export function buildAuthMethods(): AuthMethod[] {
return [
{
id: AuthType.USE_OPENAI,
name: 'Use OpenAI API key',
description: 'Requires setting the `OPENAI_API_KEY` environment variable',
type: 'terminal',
args: ['--auth-type=openai'],
},
{
id: AuthType.QWEN_OAUTH,
name: 'Qwen OAuth',
description:
'OAuth authentication for Qwen models with free daily requests',
type: 'terminal',
args: ['--auth-type=qwen-oauth'],
},
];
}
export function filterAuthMethodsById(
authMethods: AuthMethod[],
authMethodId: string,
): AuthMethod[] {
return authMethods.filter((method) => method.id === authMethodId);
}
export function pickAuthMethodsForDetails(details?: string): AuthMethod[] {
const authMethods = buildAuthMethods();
if (!details) {
return authMethods;
}
if (details.includes('qwen-oauth') || details.includes('Qwen OAuth')) {
const narrowed = filterAuthMethodsById(authMethods, AuthType.QWEN_OAUTH);
return narrowed.length ? narrowed : authMethods;
}
return authMethods;
}

View file

@ -406,9 +406,12 @@ export const agentCapabilitiesSchema = z.object({
});
export const authMethodSchema = z.object({
args: z.array(z.string()).optional(),
description: z.string().nullable(),
env: z.record(z.string()).optional(),
id: z.string(),
name: z.string(),
type: z.string().optional(),
});
export const clientResponseSchema = z.union([