fix: enhance 429 error handling and fix failed cases

This commit is contained in:
mingholy.lmh
2025-12-01 16:18:18 +08:00
parent 3056f8a63d
commit 50e3a6ee0a
7 changed files with 68 additions and 43 deletions

View File

@@ -19,6 +19,8 @@ import type {
CLIControlInitializeRequest,
CLIControlSetModelRequest,
} from '../../types.js';
import { CommandService } from '../../../services/CommandService.js';
import { BuiltinCommandLoader } from '../../../services/BuiltinCommandLoader.js';
export class SystemController extends BaseController {
/**
@@ -141,31 +143,10 @@ export class SystemController extends BaseController {
can_set_permission_mode:
typeof this.context.config.setApprovalMode === 'function',
can_set_model: typeof this.context.config.setModel === 'function',
/* TODO: sdkMcpServers support */
can_handle_mcp_message: false,
};
// Check if MCP message handling is available
try {
const mcpProvider = this.context.config as unknown as {
getMcpServers?: () => Record<string, unknown> | undefined;
};
if (typeof mcpProvider.getMcpServers === 'function') {
const servers = mcpProvider.getMcpServers();
capabilities['can_handle_mcp_message'] = Boolean(
servers && Object.keys(servers).length > 0,
);
} else {
capabilities['can_handle_mcp_message'] = false;
}
} catch (error) {
if (this.context.debugMode) {
console.error(
'[SystemController] Failed to determine MCP capability:',
error,
);
}
capabilities['can_handle_mcp_message'] = false;
}
return capabilities;
}
@@ -240,27 +221,45 @@ export class SystemController extends BaseController {
/**
* Handle supported_commands request
*
* Returns list of supported control commands
*
* Note: This list should match the ControlRequestType enum in
* packages/sdk/typescript/src/types/controlRequests.ts
* Returns list of supported slash commands loaded dynamically
*/
private async handleSupportedCommands(): Promise<Record<string, unknown>> {
const commands = [
'initialize',
'interrupt',
'set_model',
'supported_commands',
'can_use_tool',
'set_permission_mode',
'mcp_message',
'mcp_server_status',
'hook_callback',
];
const slashCommands = await this.loadSlashCommandNames();
return {
subtype: 'supported_commands',
commands,
commands: slashCommands,
};
}
/**
* Load slash command names using CommandService
*
* @returns Promise resolving to array of slash command names
*/
private async loadSlashCommandNames(): Promise<string[]> {
const controller = new AbortController();
try {
const service = await CommandService.create(
[new BuiltinCommandLoader(this.context.config)],
controller.signal,
);
const names = new Set<string>();
const commands = service.getCommands();
for (const command of commands) {
names.add(command.name);
}
return Array.from(names).sort();
} catch (error) {
if (this.context.debugMode) {
console.error(
'[SystemController] Failed to load slash commands:',
error,
);
}
return [];
} finally {
controller.abort();
}
}
}

View File

@@ -13,7 +13,11 @@ import type {
ServerGeminiStreamEvent,
TaskResultDisplay,
} from '@qwen-code/qwen-code-core';
import { GeminiEventType, ToolErrorType } from '@qwen-code/qwen-code-core';
import {
GeminiEventType,
ToolErrorType,
parseAndFormatApiError,
} from '@qwen-code/qwen-code-core';
import type { Part, GenerateContentResponseUsageMetadata } from '@google/genai';
import type {
CLIAssistantMessage,
@@ -600,6 +604,18 @@ export abstract class BaseJsonOutputAdapter {
}
this.finalizePendingBlocks(state, null);
break;
case GeminiEventType.Error: {
// Format the error message using parseAndFormatApiError for consistency
// with interactive mode error display
const errorText = parseAndFormatApiError(
event.value.error,
this.config.getContentGeneratorConfig()?.authType,
undefined,
this.config.getModel(),
);
this.appendText(state, errorText, null);
break;
}
default:
break;
}

View File

@@ -69,6 +69,7 @@ function createConfig(overrides: ConfigOverrides = {}): Config {
getDebugMode: () => false,
getApprovalMode: () => 'auto',
getOutputFormat: () => 'stream-json',
initialize: vi.fn(),
};
return { ...base, ...overrides } as unknown as Config;
}

View File

@@ -17,6 +17,7 @@ import {
OutputFormat,
InputFormat,
uiTelemetryService,
parseAndFormatApiError,
} from '@qwen-code/qwen-code-core';
import type { Content, Part, PartListUnion } from '@google/genai';
import type { CLIUserMessage, PermissionMode } from './nonInteractive/types.js';
@@ -210,6 +211,15 @@ export async function runNonInteractive(
process.stdout.write(event.value);
} else if (event.type === GeminiEventType.ToolCallRequest) {
toolCallRequests.push(event.value);
} else if (event.type === GeminiEventType.Error) {
// Format and output the error message for text mode
const errorText = parseAndFormatApiError(
event.value.error,
config.getContentGeneratorConfig()?.authType,
undefined,
config.getModel(),
);
process.stderr.write(`${errorText}\n`);
}
}
}

View File

@@ -24,7 +24,7 @@ execSync('tsc --project tsconfig.build.json', {
try {
execSync(
'npx dts-bundle-generator --project tsconfig.build.json -o dist/index.d.ts src/index.ts --no-check',
'npx dts-bundle-generator --project tsconfig.build.json -o dist/index.d.ts src/index.ts',
{
stdio: 'inherit',
cwd: rootDir,

View File

@@ -38,7 +38,6 @@ export default defineConfig({
},
testTimeout: testTimeoutMs,
hookTimeout: 10000,
globalSetup: './test/e2e/globalSetup.ts',
},
resolve: {
alias: {