Series 1 - Bài 3: CLI Layer từ keystroke đến LLM request
CLI Layer không chỉ là I/O đơn giản — nó định nghĩa UX của agent. Bài này phân tích pipeline từ keystroke đến khi request được gửi đến Agent Core, bao gồm input handling, command parsing và output streaming.
CLI Layer thường bị xem nhẹ — "chỉ là I/O, không có gì phức tạp". Nhưng đây là layer người dùng tương tác trực tiếp, và design của nó ảnh hưởng đến toàn bộ trải nghiệm làm việc với agent.
Một CLI Layer được thiết kế tốt là invisible — người dùng tập trung vào task, không phải vào cách gõ lệnh hay đọc output.
Pipeline tổng thể
Keystroke (user)
│
▼
┌─────────────────┐
│ InputHandler │
│ - readline wrap│
│ - multiline │
│ - history nav │
└────────┬────────┘
│ raw input string
▼
┌─────────────────┐
│ CommandParser │
│ /command? │
└────────┬────────┘
│
┌────┴────┐
│ │
/cmd message
│ │
▼ ▼
Handle Send to
locally AgentCore
│
│ (AgentCore processes...)
│
▼
┌─────────────────┐
│ OutputRenderer │
│ - stream chunks │
│ - tool status │
│ - ANSI format │
└────────┬────────┘
│
▼
stdout
InputHandler: Hơn là readline wrapper
Node.js built-in readline cung cấp cơ bản. InputHandler build thêm:
Multiline Input
User thường muốn paste đoạn code dài hoặc mô tả chi tiết. Terminal mặc định gửi ngay khi nhấn Enter. InputHandler cần detect khi nào đây là multiline input.
Detection strategies:
1. Bracket mode: user gõ ``` để bắt đầu, ``` để kết thúc
→ Phổ biến nhất, dễ implement
2. Trailing backslash: dòng kết thúc bằng \
→ Familiar với shell users
3. Paste detection: burst of input trong <10ms
→ Cần timing detection, phức tạp hơn
Command History
User muốn dùng phím mũi tên để navigate qua previous commands. readline có built-in history nhưng chỉ cho session hiện tại. Nếu muốn persist qua sessions, cần lưu vào file.
History management:
In-memory (session only):
readline.history.push(input)
→ Đơn giản, mất khi thoát
Persistent:
Đọc ~/.agent_history khi start
Ghi sau mỗi command
Giới hạn size (last 1000 entries)
→ Phức tạp hơn, tiện lợi hơn
Signal Handling
User cần có cách interrupt agent đang chạy mà không kill process:
Ctrl+C trong khi agent thinking → Interrupt current task
Ctrl+C trong khi agent chờ input → Exit
Ctrl+D → Graceful shutdown
CommandParser: Phân loại input
Không phải mọi input đều là message cho agent. CommandParser phân biệt:
Input starts with '/'? → Command
Otherwise → Agent message
Built-in Commands
/help → Liệt kê available commands và tools
/clear → Xóa conversation history, giữ system prompt
/context → Hiển thị current context (files đang load, token count)
/tools → Liệt kê available tools và descriptions
/exit → Graceful shutdown
/model → Hiển thị model đang dùng, có thể switch
Commands được handle hoàn toàn trong CLI Layer — Agent Core không biết về chúng.
Edge Cases
Empty input → Bỏ qua, không gửi đến agent
Whitespace only → Bỏ qua
Unknown command → Hiển thị lỗi + suggest /help
Message quá dài → Warning về token limit trước khi gửi
OutputRenderer: Streaming và formatting
Output rendering là nơi CLI trở nên có personality.
Streaming Text
Agent response được stream word by word từ LLM. Renderer nhận chunks và in trực tiếp:
Stream chunks:
"The" → print
" file" → print
" has" → print
" been" → print
" refactored." → print
[DONE] → newline
Không buffer toàn bộ response rồi mới print — đó là anti-pattern vì user phải đợi.
Tool Status Indicators
Khi agent đang execute tool, user cần biết gì đó đang xảy ra:
Agent đang think: ● thinking...
Agent gọi tool: ⚡ read_file: src/payment.service.ts
Tool thực thi xong: ✓ read_file (234 lines)
Agent gọi tool khác: ⚡ run_command: npm test
Command chạy xong: ✓ run_command (2.3s, exit 0)
Status indicators giúp user hiểu agent đang ở đâu trong process, không cảm thấy như app bị treo.
ANSI Formatting
Text thường: white
Tool names: cyan bold
File paths: yellow
Error messages: red
Success: green
Code blocks: dim background
Nhưng cần detect khi nào terminal không support ANSI — ví dụ khi output được pipe vào file:
Nếu stdout không phải TTY → disable ANSI codes
Nếu NO_COLOR env var set → disable ANSI codes
Syntax Highlighting cho Code
Agent thường return code blocks. Basic highlighting:
Detect language từ markdown fence (```typescript)
Apply keyword highlighting
Line numbers optional
Library highlight.js có thể dùng, hoặc implement basic regex-based highlighting cho performance.
Trade-off: Rich CLI vs Simple CLI
Richer CLI
│
Better UX │ Thêm dependencies
More │ Thêm code để maintain
informative│ Khó test terminal rendering
│
Simple CLI
│
Easy to │ Bare-bone experience
implement │ User cần đọc raw output
Easy to │
test │
Với internal developer tool, lean toward simple. Với tool distribute cho người khác, invest vào UX.
Kết luận
- CLI Layer pipeline: keystroke → InputHandler → CommandParser → (AgentCore) → OutputRenderer → stdout.
- InputHandler cần xử lý multiline, history, signal handling — không chỉ là readline wrapper.
- CommandParser tách meta-commands khỏi agent messages — CLI Layer handle commands, không pass xuống.
- OutputRenderer định nghĩa agent personality: streaming, tool status, formatting.
- Boundary cứng: CLI Layer không biết về LLM hay tools — chỉ biết terminal và user interaction.