Skip to main content

Your First Query

Let’s create a simple script that asks the AI to analyze a directory.

Step 1: Install the SDK

npm install @qwen-code/sdk

Step 2: Set Up Authentication

Export your API key:
export OPENAI_API_KEY="your-api-key-here"

Step 3: Create Your Script

Create a file called example.ts:
example.ts
import { query } from '@qwen-code/sdk';

// Single-turn query
const result = query({
  prompt: 'What files are in the current directory?',
  options: {
    cwd: process.cwd(),
  },
});

// Iterate over messages
for await (const message of result) {
  if (message.type === 'assistant') {
    console.log('Assistant:', message.message.content);
  } else if (message.type === 'result') {
    console.log('\nResult:', message.result);
    console.log('Tokens used:', message.usage.input_tokens + message.usage.output_tokens);
  }
}

Step 4: Run It

npx tsx example.ts
You should see the AI analyzing your directory and listing files!

Understanding the Output

The SDK emits different message types as the query progresses:

Message Types

Assistant Messages

Contain the AI’s responses:
if (message.type === 'assistant') {
  // Access content blocks
  for (const block of message.message.content) {
    if (block.type === 'text') {
      console.log(block.text);
    } else if (block.type === 'tool_use') {
      console.log(`Using tool: ${block.name}`);
    }
  }
}

System Messages

Provide session information:
if (message.type === 'system') {
  console.log('Session ID:', message.session_id);
  console.log('Working directory:', message.cwd);
  console.log('Available tools:', message.tools);
  console.log('Model:', message.model);
}

Result Messages

Indicate query completion:
if (message.type === 'result') {
  if (message.is_error) {
    console.error('Error:', message.error);
  } else {
    console.log('Success:', message.result);
    console.log('Duration:', message.duration_ms, 'ms');
    console.log('Turns:', message.num_turns);
  }
}

Adding Permission Control

By default, the SDK denies all tool executions. Let’s allow the AI to read files:
import { query } from '@qwen-code/sdk';

const result = query({
  prompt: 'Read package.json and tell me the project name',
  options: {
    cwd: process.cwd(),
    permissionMode: 'default',
    canUseTool: async (toolName, input) => {
      // Allow read operations
      if (toolName === 'read_file') {
        console.log(`Allowing read of: ${input.path}`);
        return { behavior: 'allow', updatedInput: input };
      }
      
      // Deny everything else
      return { 
        behavior: 'deny', 
        message: `${toolName} is not allowed` 
      };
    },
  },
});

for await (const message of result) {
  if (message.type === 'assistant') {
    console.log('Assistant:', message.message.content);
  }
}

Using Permission Modes

Instead of writing a custom canUseTool handler, use built-in permission modes:

YOLO Mode (Auto-Approve Everything)

const result = query({
  prompt: 'Create a hello.txt file with "Hello World"',
  options: {
    permissionMode: 'yolo', // ⚠️ Use with caution!
  },
});

Auto-Edit Mode (Auto-Approve Edits)

const result = query({
  prompt: 'Fix the syntax errors in main.ts',
  options: {
    permissionMode: 'auto-edit', // Auto-approves edit and write_file
  },
});

Plan Mode (Block All Writes)

const result = query({
  prompt: 'How would you refactor this code?',
  options: {
    permissionMode: 'plan', // AI will explain instead of executing
  },
});

Streaming Partial Messages

Get real-time updates as the AI generates responses:
import { query, isSDKPartialAssistantMessage } from '@qwen-code/sdk';

const result = query({
  prompt: 'Explain how async/await works',
  options: {
    includePartialMessages: true,
  },
});

for await (const message of result) {
  if (isSDKPartialAssistantMessage(message)) {
    const event = message.event;
    
    if (event.type === 'content_block_delta') {
      if (event.delta.type === 'text_delta') {
        // Stream text as it's generated
        process.stdout.write(event.delta.text);
      }
    }
  } else if (message.type === 'assistant') {
    console.log('\n[Complete message received]');
  }
}

Aborting Queries

Cancel long-running queries:
import { query, isAbortError } from '@qwen-code/sdk';

const abortController = new AbortController();

const result = query({
  prompt: 'Analyze this large codebase...',
  options: {
    abortController,
  },
});

// Abort after 10 seconds
setTimeout(() => {
  console.log('Aborting...');
  abortController.abort();
}, 10000);

try {
  for await (const message of result) {
    console.log(message);
  }
} catch (error) {
  if (isAbortError(error)) {
    console.log('Query was aborted by user');
  } else {
    throw error;
  }
}

Complete Example

Here’s a complete example that demonstrates multiple features:
complete-example.ts
import { query, isSDKAssistantMessage, isSDKResultMessage } from '@qwen-code/sdk';

async function main() {
  const abortController = new AbortController();
  
  const result = query({
    prompt: 'List the TypeScript files in the src directory',
    options: {
      cwd: process.cwd(),
      permissionMode: 'default',
      model: 'gpt-4',
      abortController,
      
      canUseTool: async (toolName, input, { signal }) => {
        // Allow safe read operations
        const readTools = ['list_directory', 'read_file', 'list_files'];
        if (readTools.includes(toolName)) {
          return { behavior: 'allow', updatedInput: input };
        }
        
        return { behavior: 'deny', message: 'Only read operations allowed' };
      },
      
      stderr: (message) => {
        console.error('[CLI Error]:', message);
      },
    },
  });
  
  console.log('Session ID:', result.getSessionId());
  
  for await (const message of result) {
    if (isSDKAssistantMessage(message)) {
      console.log('\n--- Assistant Message ---');
      
      for (const block of message.message.content) {
        if (block.type === 'text') {
          console.log(block.text);
        } else if (block.type === 'thinking') {
          console.log('[Thinking]:', block.thinking);
        } else if (block.type === 'tool_use') {
          console.log(`[Tool]: ${block.name}(${JSON.stringify(block.input)})`);
        }
      }
    } else if (isSDKResultMessage(message)) {
      console.log('\n--- Result ---');
      if (message.is_error) {
        console.error('Error:', message.error);
      } else {
        console.log('Success:', message.result);
        console.log(`Took ${message.duration_ms}ms (${message.num_turns} turns)`);
        console.log('Tokens:', message.usage);
      }
    }
  }
  
  console.log('\nQuery closed:', result.isClosed());
}

main().catch(console.error);
Run it:
npx tsx complete-example.ts

Next Steps

API Reference

Learn about all available options

Message Types

Understand different message types

Permission Modes

Master permission control

Examples

Explore more examples