Bỏ qua để đến nội dung

Tích hợp SDK

Thời gian học: ~35 phút

Yêu cầu trước: Module 11.1 (Headless Mode)

Kết quả: Sau module này, bạn sẽ integrate được Claude Code vào Node.js/Python app, hiểu SDK vs subprocess, và biết khi nào dùng approach nào.


Bạn build Node.js tool cần generate code. Dùng child_process.exec('claude -p ...') works nhưng hacky. Parse stdout rất fragile. Error handling messy. Bạn muốn gọi Claude như proper library — clean API, structured output, error handling chuẩn.

Ví von: CLI subprocess như gọi taxi bằng điện thoại — works nhưng clunky. SDK như app Grab — smooth, structured, real-time tracking.


AspectCLI (subprocess)SDK
Invocationexec('claude ...')import { claude }
OutputParse string từ stdoutStructured object
ErrorsCheck exit codeException/Promise reject
StreamingPipe phức tạpNative callback
Type safetyKhông cóTypeScript support
const { execSync } = require('child_process');
function callClaude(prompt, options = {}) {
const escapedPrompt = prompt.replace(/"/g, '\\"');
const cmd = `claude -p "${escapedPrompt}"`;
const result = execSync(cmd, {
encoding: 'utf8',
timeout: options.timeout || 30000,
maxBuffer: 10 * 1024 * 1024
});
return result.trim();
}
import subprocess
def call_claude(prompt, timeout=30):
result = subprocess.run(
['claude', '-p', prompt],
capture_output=True,
text=True,
timeout=timeout
)
if result.returncode != 0:
raise Exception(f"Claude failed: {result.stderr}")
return result.stdout.strip()
  • Subprocess: Quick script, fallback khi SDK chưa có
  • SDK (khi có): Production app, complex workflow, cần streaming
  • Hiện tại: Claude Code chưa có official SDK → subprocess là cách chính

Khi nào nên dùng CLI trực tiếp vs xây dựng SDK wrapper? Ma trận quyết định này giúp bạn chọn:

Khía CạnhCLI (claude -p)SDK (subprocess wrapper)SDK (chính thức)
Thời gian setupZero — đã cài sẵn30 phút — viết wrapper code5 phút — npm install
Xử lý lỗiChỉ exit codesCustom try/catchExceptions tích hợp
Định dạng outputText thô ra stdoutTự parse stdoutStructured objects
StreamingPipe ra filespawn + event listenersNative callbacks
Type safetyKhông cóĐịnh nghĩa types thủ côngFull TypeScript types
Concurrency& chạy nềnPromise.all / workersAsync/await native
Phù hợp nhấtScript nhanh, CI/CDProduction tools hiện tạiProduction tools (khi stable)
Bảo trìThấp — chỉ bashTrung bình — wrapper codeThấp — cập nhật library
Đây là script nhanh hoặc CI/CD job?
CÓ → CLI (`claude -p` trong bash)
KHÔNG → Bạn cần structured output hoặc xử lý lỗi?
KHÔNG → CLI vẫn ổn
CÓ → Bạn cần streaming hoặc tiến trình real-time?
KHÔNG → subprocess wrapper với execSync
CÓ → subprocess wrapper với spawn + event listeners

Với hầu hết team hiện tại: bắt đầu với CLI cho scripts và CI/CD pipelines, xây subprocess wrapper khi cần tích hợp vào ứng dụng Node.js hoặc Python, và lên kế hoạch chuyển sang SDK chính thức khi nó đạt bản stable. Subprocess wrapper bạn xây hôm nay đóng vai trò như bản đặc tả cho những gì bạn cần từ SDK — giúp việc chuyển đổi trở nên đơn giản.


Scenario: Node.js tool generate unit test từ source file.

lib/claude-wrapper.js
const { execSync } = require('child_process');
class ClaudeWrapper {
constructor(options = {}) {
this.timeout = options.timeout || 60000;
this.maxRetries = options.maxRetries || 2;
}
executeSync(prompt) {
const escapedPrompt = prompt.replace(/"/g, '\\"');
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
try {
const result = execSync(`claude -p "${escapedPrompt}"`, {
encoding: 'utf8',
timeout: this.timeout,
maxBuffer: 10 * 1024 * 1024
});
return { success: true, output: result.trim() };
} catch (error) {
if (attempt >= this.maxRetries - 1) {
return { success: false, error: error.message };
}
}
}
}
}
module.exports = ClaudeWrapper;
generate-tests.js
const fs = require('fs');
const ClaudeWrapper = require('./lib/claude-wrapper');
const claude = new ClaudeWrapper({ timeout: 90000 });
function generateTests(sourceFile) {
const code = fs.readFileSync(sourceFile, 'utf8');
const prompt = `Generate Jest unit tests for:\n\n${code}\n\nReturn ONLY test code.`;
const result = claude.executeSync(prompt);
if (!result.success) {
console.error('Error:', result.error);
process.exit(1);
}
const testFile = sourceFile.replace(/\.js$/, '.test.js');
fs.writeFileSync(testFile, result.output);
console.log(`✅ Generated: ${testFile}`);
}
generateTests(process.argv[2]);

Expected output:

$ node generate-tests.js src/calculator.js
✅ Generated: src/calculator.test.js
generate_tests.py
import subprocess
import sys
def call_claude(prompt, timeout=60):
result = subprocess.run(
['claude', '-p', prompt],
capture_output=True,
text=True,
timeout=timeout
)
if result.returncode != 0:
raise Exception(result.stderr)
return result.stdout.strip()
def generate_tests(source_file):
with open(source_file, 'r') as f:
code = f.read()
prompt = f"Generate pytest tests for:\n\n{code}"
tests = call_claude(prompt, timeout=90)
test_file = source_file.replace('.py', '_test.py')
with open(test_file, 'w') as f:
f.write(tests)
print(f"✅ Generated: {test_file}")
if __name__ == '__main__':
generate_tests(sys.argv[1])

Expected output:

$ python generate_tests.py src/calculator.py
✅ Generated: src/calculator_test.py

Goal: Build wrapper với exponential backoff retry.

Instructions:

  1. Create ClaudeClient class với execute(prompt) method
  2. Retry 3 lần với delay: 2s, 4s, 8s
  3. Return structured error type
💡 Hint

Exponential backoff: Math.pow(2, attempt) * 1000. Sleep with execSync('sleep N').

✅ Solution
class ClaudeClient {
execute(prompt, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const result = execSync(`claude -p "${prompt.replace(/"/g, '\\"')}"`, {
encoding: 'utf8',
timeout: 60000,
maxBuffer: 10 * 1024 * 1024
});
return { type: 'success', output: result.trim() };
} catch (error) {
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt + 1);
execSync(`sleep ${delay}`);
} else {
return { type: error.killed ? 'timeout' : 'error', error: error.message };
}
}
}
}
}

Goal: Process nhiều files, continue khi 1 file fail.

Instructions:

  1. Accept array file paths
  2. Process với max 3 concurrent
  3. Return summary: successes, failures
💡 Hint

Dùng Promise.allSettled() để không fail toàn bộ khi 1 task fail.

✅ Solution
async function batchProcess(files) {
const results = await Promise.allSettled(
files.map(f => generateTests(f))
);
const successes = results.filter(r => r.status === 'fulfilled').length;
const failures = results.filter(r => r.status === 'rejected').length;
console.log(`${successes} succeeded, ❌ ${failures} failed`);
return { successes, failures };
}

MethodUse CaseBlocking?
execSync()Quick scriptYes
exec()Async callbackNo
spawn()Streaming, large outputNo
MethodUse CaseBlocking?
subprocess.run()Standard choiceYes
subprocess.Popen()StreamingNo
Node.js
try {
execSync(cmd, { timeout: 30000 });
} catch (err) {
if (err.killed) console.error('Timeout');
else console.error('Error:', err.status);
}
# Python
try:
result = subprocess.run([...], timeout=30)
except subprocess.TimeoutExpired:
print("Timeout!")

❌ Mistake✅ Correct Approach
Assume SDK existsVerify docs, subprocess luôn work
Không escape quoteprompt.replace(/"/g, '\\"')
Ignore exit codeCheck returncode !== 0
Block main threadDùng spawn async
Default maxBuffer nhỏSet maxBuffer: 10 * 1024 * 1024
Không timeoutAlways set timeout (30-90s)

Scenario: Agency Việt Nam build code review tool integrate Claude vào CI pipeline.

Problem: Không có SDK. Cần reliable integration với GitHub webhook.

Solution:

  • ClaudeReviewService wrap subprocess
  • PR webhook trigger review job
  • Store result vào PostgreSQL
  • Dashboard show issue

Result:

  • 200+ PR reviewed/month automatic
  • Catch 15% more issues
  • Dev nhận instant feedback

Quote: “Không đợi perfect SDK. Subprocess được 90%. Ship first, optimize later.”


Tiếp theo: Module 11.3: Hệ thống Hook