Skip to content

Commit 6e826b8

Browse files
authored
Merge pull request #1165 from QwenLM/mingholy/fix/sdk-timeout
fix: update timeout settings and default logging level in SDK
2 parents ab228c6 + 86b166b commit 6e826b8

File tree

5 files changed

+44
-25
lines changed

5 files changed

+44
-25
lines changed

packages/sdk-typescript/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Creates a new query session with the Qwen Code.
5959
| `model` | `string` | - | The AI model to use (e.g., `'qwen-max'`, `'qwen-plus'`, `'qwen-turbo'`). Takes precedence over `OPENAI_MODEL` and `QWEN_MODEL` environment variables. |
6060
| `pathToQwenExecutable` | `string` | Auto-detected | Path to the Qwen Code executable. Supports multiple formats: `'qwen'` (native binary from PATH), `'/path/to/qwen'` (explicit path), `'/path/to/cli.js'` (Node.js bundle), `'node:/path/to/cli.js'` (force Node.js runtime), `'bun:/path/to/cli.js'` (force Bun runtime). If not provided, auto-detects from: `QWEN_CODE_CLI_PATH` env var, `~/.volta/bin/qwen`, `~/.npm-global/bin/qwen`, `/usr/local/bin/qwen`, `~/.local/bin/qwen`, `~/node_modules/.bin/qwen`, `~/.yarn/bin/qwen`. |
6161
| `permissionMode` | `'default' \| 'plan' \| 'auto-edit' \| 'yolo'` | `'default'` | Permission mode controlling tool execution approval. See [Permission Modes](#permission-modes) for details. |
62-
| `canUseTool` | `CanUseTool` | - | Custom permission handler for tool execution approval. Invoked when a tool requires confirmation. Must respond within 30 seconds or the request will be auto-denied. See [Custom Permission Handler](#custom-permission-handler). |
62+
| `canUseTool` | `CanUseTool` | - | Custom permission handler for tool execution approval. Invoked when a tool requires confirmation. Must respond within 60 seconds or the request will be auto-denied. See [Custom Permission Handler](#custom-permission-handler). |
6363
| `env` | `Record<string, string>` | - | Environment variables to pass to the Qwen Code process. Merged with the current process environment. |
6464
| `mcpServers` | `Record<string, McpServerConfig>` | - | MCP (Model Context Protocol) servers to connect. Supports external servers (stdio/SSE/HTTP) and SDK-embedded servers. External servers are configured with transport options like `command`, `args`, `url`, `httpUrl`, etc. SDK servers use `{ type: 'sdk', name: string, instance: Server }`. |
6565
| `abortController` | `AbortController` | - | Controller to cancel the query session. Call `abortController.abort()` to terminate the session and cleanup resources. |
@@ -76,12 +76,12 @@ Creates a new query session with the Qwen Code.
7676

7777
The SDK enforces the following default timeouts:
7878

79-
| Timeout | Default | Description |
80-
| ---------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
81-
| `canUseTool` | 30 seconds | Maximum time for `canUseTool` callback to respond. If exceeded, the tool request is auto-denied. |
82-
| `mcpRequest` | 1 minute | Maximum time for SDK MCP tool calls to complete. |
83-
| `controlRequest` | 30 seconds | Maximum time for control operations like `initialize()`, `setModel()`, `setPermissionMode()`, and `interrupt()` to complete. |
84-
| `streamClose` | 1 minute | Maximum time to wait for initialization to complete before closing CLI stdin in multi-turn mode with SDK MCP servers. |
79+
| Timeout | Default | Description |
80+
| ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
81+
| `canUseTool` | 1 minute | Maximum time for `canUseTool` callback to respond. If exceeded, the tool request is auto-denied. |
82+
| `mcpRequest` | 1 minute | Maximum time for SDK MCP tool calls to complete. |
83+
| `controlRequest` | 1 minute | Maximum time for control operations like `initialize()`, `setModel()`, `setPermissionMode()`, and `interrupt()` to complete. |
84+
| `streamClose` | 1 minute | Maximum time to wait for initialization to complete before closing CLI stdin in multi-turn mode with SDK MCP servers. |
8585

8686
You can customize these timeouts via the `timeout` option:
8787

packages/sdk-typescript/src/query/Query.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
* Implements AsyncIterator protocol for message consumption.
66
*/
77

8-
const DEFAULT_CAN_USE_TOOL_TIMEOUT = 30_000;
8+
const DEFAULT_CAN_USE_TOOL_TIMEOUT = 60_000;
99
const DEFAULT_MCP_REQUEST_TIMEOUT = 60_000;
10-
const DEFAULT_CONTROL_REQUEST_TIMEOUT = 30_000;
10+
const DEFAULT_CONTROL_REQUEST_TIMEOUT = 60_000;
1111
const DEFAULT_STREAM_CLOSE_TIMEOUT = 60_000;
1212

1313
import { randomUUID } from 'node:crypto';
@@ -434,8 +434,9 @@ export class Query implements AsyncIterable<SDKMessage> {
434434
try {
435435
const canUseToolTimeout =
436436
this.options.timeout?.canUseTool ?? DEFAULT_CAN_USE_TOOL_TIMEOUT;
437+
let timeoutId: NodeJS.Timeout | undefined;
437438
const timeoutPromise = new Promise<never>((_, reject) => {
438-
setTimeout(
439+
timeoutId = setTimeout(
439440
() => reject(new Error('Permission callback timeout')),
440441
canUseToolTimeout,
441442
);
@@ -451,6 +452,10 @@ export class Query implements AsyncIterable<SDKMessage> {
451452
timeoutPromise,
452453
]);
453454

455+
if (timeoutId) {
456+
clearTimeout(timeoutId);
457+
}
458+
454459
if (result.behavior === 'allow') {
455460
return {
456461
behavior: 'allow',
@@ -789,14 +794,20 @@ export class Query implements AsyncIterable<SDKMessage> {
789794
) {
790795
const streamCloseTimeout =
791796
this.options.timeout?.streamClose ?? DEFAULT_STREAM_CLOSE_TIMEOUT;
792-
await Promise.race([
793-
this.firstResultReceivedPromise,
794-
new Promise<void>((resolve) => {
795-
setTimeout(() => {
796-
resolve();
797-
}, streamCloseTimeout);
798-
}),
799-
]);
797+
let timeoutId: NodeJS.Timeout | undefined;
798+
799+
const timeoutPromise = new Promise<void>((resolve) => {
800+
timeoutId = setTimeout(() => {
801+
logger.info('streamCloseTimeout resolved');
802+
resolve();
803+
}, streamCloseTimeout);
804+
});
805+
806+
await Promise.race([this.firstResultReceivedPromise, timeoutPromise]);
807+
808+
if (timeoutId) {
809+
clearTimeout(timeoutId);
810+
}
800811
}
801812

802813
this.endInput();

packages/sdk-typescript/src/types/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ export interface QueryOptions {
316316
/**
317317
* Logging level for the SDK.
318318
* Controls the verbosity of log messages output by the SDK.
319-
* @default 'info'
319+
* @default 'error'
320320
*/
321321
logLevel?: 'debug' | 'info' | 'warn' | 'error';
322322

packages/sdk-typescript/src/utils/logger.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
2222

2323
export class SdkLogger {
2424
private static config: LoggerConfig = {};
25-
private static effectiveLevel: LogLevel = 'info';
25+
private static effectiveLevel: LogLevel = 'error';
2626

2727
static configure(config: LoggerConfig): void {
2828
this.config = config;
@@ -47,7 +47,7 @@ export class SdkLogger {
4747
return 'debug';
4848
}
4949

50-
return 'info';
50+
return 'error';
5151
}
5252

5353
private static isValidLogLevel(level: string): boolean {

packages/sdk-typescript/test/unit/Query.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -542,13 +542,16 @@ describe('Query', () => {
542542
const canUseTool = vi.fn().mockImplementation(
543543
() =>
544544
new Promise((resolve) => {
545-
setTimeout(() => resolve({ behavior: 'allow' }), 35000); // Exceeds 30s timeout
545+
setTimeout(() => resolve({ behavior: 'allow' }), 15000);
546546
}),
547547
);
548548

549549
const query = new Query(transport, {
550550
cwd: '/test',
551551
canUseTool,
552+
timeout: {
553+
canUseTool: 10000,
554+
},
552555
});
553556

554557
const controlReq = createControlRequest('can_use_tool', 'perm-req-4');
@@ -567,7 +570,7 @@ describe('Query', () => {
567570
});
568571
}
569572
},
570-
{ timeout: 35000 },
573+
{ timeout: 15000 },
571574
);
572575

573576
await query.close();
@@ -1204,7 +1207,12 @@ describe('Query', () => {
12041207
});
12051208

12061209
it('should handle control request timeout', async () => {
1207-
const query = new Query(transport, { cwd: '/test' });
1210+
const query = new Query(transport, {
1211+
cwd: '/test',
1212+
timeout: {
1213+
controlRequest: 10000,
1214+
},
1215+
});
12081216

12091217
// Respond to initialize
12101218
await vi.waitFor(() => {
@@ -1224,7 +1232,7 @@ describe('Query', () => {
12241232
await expect(interruptPromise).rejects.toThrow(/timeout/i);
12251233

12261234
await query.close();
1227-
}, 35000);
1235+
}, 15000);
12281236

12291237
it('should handle malformed control responses', async () => {
12301238
const query = new Query(transport, { cwd: '/test' });

0 commit comments

Comments
 (0)