Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/sdk-python/src/qwen_code_sdk/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,16 @@ def build_cli_arguments(options: QueryOptions) -> list[str]:
elif options.session_id:
args.extend(["--session-id", options.session_id])

if options.sandbox:
args.append("--sandbox")

if options.safe_mode:
args.append("--safe-mode")

if options.insecure:
args.append("--insecure")

if options.worktree:
args.append("--worktree")

return args
12 changes: 12 additions & 0 deletions packages/sdk-python/src/qwen_code_sdk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ class QueryOptionsDict(TypedDict, total=False):
timeout: TimeoutOptionsDict
mcp_servers: dict[str, dict[str, Any]]
stderr: Callable[[str], None]
sandbox: bool
safe_mode: bool
insecure: bool
worktree: bool


@dataclass
Expand All @@ -139,6 +143,10 @@ class QueryOptions:
timeout: TimeoutOptions = TimeoutOptions()
mcp_servers: dict[str, dict[str, Any]] | None = None
stderr: Callable[[str], None] | None = None
sandbox: bool = False
safe_mode: bool = False
insecure: bool = False
worktree: bool = False

@classmethod
def from_mapping(cls, value: Mapping[str, Any] | None) -> QueryOptions:
Expand Down Expand Up @@ -183,6 +191,10 @@ def from_mapping(cls, value: Mapping[str, Any] | None) -> QueryOptions:
Callable[[str], None] | None,
_as_optional_callable(data, "stderr"),
),
sandbox=_as_optional_bool(data, "sandbox") or False,
safe_mode=_as_optional_bool(data, "safe_mode") or False,
insecure=_as_optional_bool(data, "insecure") or False,
worktree=_as_optional_bool(data, "worktree") or False,
)


Expand Down
32 changes: 32 additions & 0 deletions packages/sdk-python/tests/unit/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,38 @@ def test_cli_argument_precedence_prefers_resume_then_continue_then_session_id()
assert "--session-id" not in args


def test_build_cli_arguments_includes_boolean_flags() -> None:
args = build_cli_arguments(
QueryOptions(
sandbox=True,
safe_mode=True,
insecure=True,
worktree=True,
)
)

assert "--sandbox" in args
assert "--safe-mode" in args
assert "--insecure" in args
assert "--worktree" in args


def test_build_cli_arguments_omits_false_boolean_flags() -> None:
args = build_cli_arguments(
QueryOptions(
sandbox=False,
safe_mode=False,
insecure=False,
worktree=False,
)
)

assert "--sandbox" not in args
assert "--safe-mode" not in args
assert "--insecure" not in args
assert "--worktree" not in args


def test_prepare_spawn_info_uses_runtime_for_python_scripts(tmp_path: Path) -> None:
script_path = tmp_path / "fake-qwen.py"
script_path.write_text("print('ok')\n", encoding="utf-8")
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk-typescript/src/query/createQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export function query({
includePartialMessages: options.includePartialMessages,
resume: options.resume,
sessionId,
sandbox: options.sandbox,
safeMode: options.safeMode,
insecure: options.insecure,
worktree: options.worktree,
});

const queryOptions: QueryOptions = {
Expand Down
16 changes: 16 additions & 0 deletions packages/sdk-typescript/src/transport/ProcessTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,22 @@ export class ProcessTransport implements Transport {
args.push('--session-id', this.options.sessionId);
}

if (this.options.sandbox) {
args.push('--sandbox');
}

if (this.options.safeMode) {
args.push('--safe-mode');
}

if (this.options.insecure) {
args.push('--insecure');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The CLI logs a prominent warning to stderr when --insecure is passed, but the SDK's default stderr handling ('ignore' unless debug: true or a stderr callback is configured) silently discards it. An SDK consumer enabling insecure: true programmatically gets no visible indication that TLS verification is disabled.

Consider emitting an SDK-side warning:

if (this.options.insecure) {
  console.warn('[qwen-code-sdk] TLS certificate verification is disabled (--insecure)');
}

The same concern applies to the Python SDK's transport.py, where stderr defaults to subprocess.DEVNULL.

— qwen3.7-max via Qwen Code /review

}

if (this.options.worktree) {
args.push('--worktree');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The CLI defines --worktree as type: 'string' in yargs (config.ts:881), but the SDK appends it as a bare --worktree with no value. This works today because --worktree is the last argument — yargs treats the missing value as "". However, if any future change appends another flag after --worktree, yargs will silently consume it as the worktree slug.

Passing an explicit empty string makes this position-independent:

Suggested change
args.push('--worktree');
if (this.options.worktree) {
args.push('--worktree', '');
}

Additionally, the CLI supports slug (--worktree my-feature) and PR reference (--worktree=#123) modes that the SDK's boolean type cannot express. Consider typing worktree as boolean | string to expose the CLI's full capability, and emitting --worktree <value> when a string is provided.

The same fix should be applied to the Python SDK's transport.py.

— qwen3.7-max via Qwen Code /review

}

return args;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/sdk-typescript/src/types/queryOptionsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ export const QueryOptionsSchema = z
includePartialMessages: z.boolean().optional(),
resume: z.string().optional(),
sessionId: z.string().optional(),
sandbox: z.boolean().optional(),
safeMode: z.boolean().optional(),
insecure: z.boolean().optional(),
worktree: z.boolean().optional(),
timeout: TimeoutConfigSchema.optional(),
})
.strict();
34 changes: 34 additions & 0 deletions packages/sdk-typescript/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export type TransportOptions = {
* When resume is provided, this should match the resume ID.
*/
sessionId?: string;
sandbox?: boolean;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] These four new fields in TransportOptions lack JSDoc, while the same fields in QueryOptions (lines 469-503) have full documentation with descriptions, @default values, and a security warning on insecure. Neighboring fields in this same type (continue, resume, sessionId) all have JSDoc blocks.

Consider adding JSDoc to match QueryOptions, or at minimum a one-line description and @default false for each. The insecure field especially benefits from the MITM warning at the type surface.

— qwen3.7-max via Qwen Code /review

safeMode?: boolean;
insecure?: boolean;
worktree?: boolean;
};

export interface QuerySystemPromptPreset {
Expand Down Expand Up @@ -465,6 +469,36 @@ export interface QueryOptions {
*/
sessionId?: string;

/**
* Run in sandbox mode.
* Equivalent to CLI's `--sandbox` flag.
* @default false
*/
sandbox?: boolean;

/**
* Disable all customizations (context files, hooks, extensions, skills, MCP servers)
* for troubleshooting.
* Equivalent to CLI's `--safe-mode` flag.
* @default false
*/
safeMode?: boolean;

/**
* Skip TLS certificate verification for API connections.
* Equivalent to CLI's `--insecure` flag.
* WARNING: Removes protection against man-in-the-middle attacks.
* @default false
*/
insecure?: boolean;

/**
* Enable Git worktree mode.
* Equivalent to CLI's `--worktree` flag.
* @default false
*/
worktree?: boolean;

/**
* Timeout configuration for various SDK operations.
* All values are in milliseconds.
Expand Down
57 changes: 57 additions & 0 deletions packages/sdk-typescript/test/unit/ProcessTransport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,63 @@ describe('ProcessTransport', () => {
);
});

it('should include boolean flags when set to true', () => {
mockPrepareSpawnInfo.mockReturnValue({
command: 'qwen',
args: [],
type: 'native',
originalInput: 'qwen',
});
mockSpawn.mockReturnValue(mockChildProcess);

const options: TransportOptions = {
pathToQwenExecutable: 'qwen',
sandbox: true,
safeMode: true,
insecure: true,
worktree: true,
};

new ProcessTransport(options);

expect(mockSpawn).toHaveBeenCalledWith(
'qwen',
expect.arrayContaining([
'--sandbox',
'--safe-mode',
'--insecure',
'--worktree',
]),
expect.any(Object),
);
});

it('should omit boolean flags when set to false', () => {
mockPrepareSpawnInfo.mockReturnValue({
command: 'qwen',
args: [],
type: 'native',
originalInput: 'qwen',
});
mockSpawn.mockReturnValue(mockChildProcess);

const options: TransportOptions = {
pathToQwenExecutable: 'qwen',
sandbox: false,
safeMode: false,
insecure: false,
worktree: false,
};

new ProcessTransport(options);

const spawnCall = mockSpawn.mock.calls[0]?.[1] as string[];
expect(spawnCall).not.toContain('--sandbox');
expect(spawnCall).not.toContain('--safe-mode');
expect(spawnCall).not.toContain('--insecure');
expect(spawnCall).not.toContain('--worktree');
});

it('should throw if aborted before initialization', () => {
mockPrepareSpawnInfo.mockReturnValue({
command: 'qwen',
Expand Down