建置 coding agent
本指南將帶你建置一個與 Mastra Code、Claude Code 或 Codex 同類的小型 coding agent 應用程式。你會使用 buildBasePrompt() 與 createCodingAgent() 建立 coding agent,將它包裝在 AgentController 中以支援互動式 session 與 Tool 核准,並在使用 pi-tui 建置的終端機 UI 中執行 controller。
下方影片展示你將建置的 coding agent 實際運作情形。
先決條件「先決條件」的直接連結
- 已安裝 Node.js
v22.19.0或更新版本 - 受支援的 Model Provider 所提供的 API 金鑰
- 現有的 Mastra 專案。如有需要,請依照安裝指南操作。
安裝終端機相依套件「安裝終端機相依套件」的直接連結
安裝 pi-tui 與 tsx:
- npm
- pnpm
- Yarn
- Bun
npm install @earendil-works/pi-tui
npm install --save-dev tsx
pnpm add @earendil-works/pi-tui
pnpm add --save-dev tsx
yarn add @earendil-works/pi-tui
yarn add --dev tsx
bun add @earendil-works/pi-tui
bun add --dev tsx
pi-tui 提供終端機 renderer 與輸入編輯器。tsx 會直接執行 TypeScript 進入點。
建立 coding agent「建立 coding agent」的直接連結
建立 src/mastra/agents/coding-agent.ts。prompt 會描述目前的專案,並將 prompt 中的一般 Tool 名稱對應至預設 Workspace 提供的 Tool。
import { basename } from 'node:path'
import { buildBasePrompt, createCodingAgent } from '@mastra/core/coding-agent'
export const projectPath = process.cwd()
const model = 'openai/gpt-5.6-sol'
const instructions = buildBasePrompt({
projectPath,
projectName: basename(projectPath),
platform: process.platform,
date: new Date().toISOString().slice(0, 10),
mode: 'build',
modelId: model,
productName: 'My Coding Agent',
coAuthorName: 'My Coding Agent',
coAuthorEmail: 'coding-agent@example.com',
toolGuidance: `# Workspace tools
- Use mastra_workspace_read_file for view.
- Use mastra_workspace_list_files for find_files.
- Use mastra_workspace_grep for search_content.
- Use mastra_workspace_execute_command for execute_command.
- Use mastra_workspace_write_file, mastra_workspace_edit_file, and mastra_workspace_file_stat for writing, editing, and inspecting file metadata.
- Use only the workspace tools provided to you. Do not attempt unavailable capabilities.`,
})
export const codingAgent = createCodingAgent({
id: 'coding-agent',
name: 'Coding Agent',
model,
instructions,
basePath: projectPath,
})
請將品牌相關值替換為 Agent 的名稱與共同作者詳細資料。createCodingAgent() 會提供本機 filesystem 與 Sandbox Workspace,以及從暫時性 Provider 錯誤中復原的預設設定。basePath 會限定 filesystem Tool 的作用範圍,並設定命令的初始工作目錄。你傳給 factory 的任何設定都會優先於其預設值。
預設的本機 Sandbox 會直接在 host 上執行命令,不提供隔離,因此 basePath 並非作業系統層級的安全邊界。此範例在終端機中加入逐次 Tool 呼叫核准,但你仍應只針對可信任的本機專案執行。
註冊 coding agent「註冊 coding agent」的直接連結
在 src/mastra/index.ts 中,像註冊其他 Mastra Agent 一樣註冊回傳的 Agent。註冊後,也能在 Studio 與透過 Mastra server 使用底層 Agent。
import { Mastra } from '@mastra/core/mastra'
import { codingAgent } from './agents/coding-agent'
export const mastra = new Mastra({
agents: { codingAgent },
})
測試 coding agent「測試 coding agent」的直接連結
新增終端機介面前,請先在 Studio 中驗證底層 Agent。開發伺服器啟動時,其工作目錄是 src/mastra/public,因此請在該處加入不含敏感資訊的檔案供 Agent 檢查:
# Project notes
Name: Acme support portal
Status: In development
Owner: Platform team
啟動開發伺服器:
- npm
- pnpm
- Yarn
- Bun
npm run dev
pnpm run dev
yarn dev
bun run dev
開啟 Studio,選取 Coding Agent,然後輸入:
Inspect project-notes.md and report the project name, status, and owner. Do not modify files.
回應應辨識出 Acme support portal 並說明其開發狀態,也應指出專案由 Platform team 負責,且不變更檔案。Model 的措辭可能有所不同。
建立 Agent controller「建立 Agent controller」的直接連結
建立 src/mastra/coding-agent-controller.ts。controller 負責管理互動式 session、公開 UI 事件,並暫停 Workspace Tool 以等待核准。
import { AgentController } from '@mastra/core/agent-controller'
import { codingAgent, projectPath } from './agents/coding-agent'
export async function createCodingAgentSession() {
const workspace = await codingAgent.getWorkspace()
if (!workspace) {
throw new Error('The coding agent requires a workspace.')
}
const controller = new AgentController({
id: 'coding-agent-controller',
agent: codingAgent,
workspace,
modes: [{ id: 'build', name: 'Build', metadata: { default: true } }],
disableBuiltinTools: [
'ask_user',
'submit_plan',
'task_write',
'task_update',
'task_complete',
'task_check',
'subagent',
],
})
await controller.init()
const session = await controller.createSession({
id: 'local-session',
ownerId: 'local-user',
resourceId: projectPath,
})
return { controller, session }
}
此範例使用一種 mode,並停用 controller 的其他內建 Tool,讓入門 UI 能專注於 Workspace 執行。這個簡化設定是為本教學而設計。在正式環境的應用程式中,請啟用產品所需的內建 Tool 並實作其 UI 流程:ask_user 與 submit_plan 等互動式 Tool 會暫停,直到介面恢復執行;task 與 subagent Tool 則有各自的生命週期事件。請參閱 Tool 核准與暫停。
此範例也省略了 storage,因此對話只會在目前處理程序存續期間保留。日後需要恢復 session 時,可以再加入 storage。
建置終端機 UI「建置終端機 UI」的直接連結
建立 src/coding-agent-tui.ts。UI 會 render 助理訊息更新並顯示 Tool 活動,也會要求使用者核准或拒絕每次 Workspace Tool 呼叫。
import { pathToFileURL } from 'node:url'
import {
Editor,
matchesKey,
ProcessTerminal,
Text,
TUI,
type EditorTheme,
type Terminal,
} from '@earendil-works/pi-tui'
import { createCodingAgentSession } from './mastra/coding-agent-controller'
const plain = (text: string) => text
const editorTheme: EditorTheme = {
borderColor: plain,
selectList: {
selectedPrefix: plain,
selectedText: plain,
description: plain,
scrollInfo: plain,
noMatch: plain,
},
}
function getText(message: { content: Array<{ type: string; text?: string }> }) {
return message.content
.filter(part => part.type === 'text')
.map(part => part.text ?? '')
.join('')
}
export async function startCodingAgentTui(terminal: Terminal = new ProcessTerminal()) {
const { controller, session } = await createCodingAgentSession()
const tui = new TUI(terminal)
const output = new Text('Ask me to inspect or change this project.', 1, 0)
const editor = new Editor(tui, editorTheme)
let busy = false
let pendingApproval: { toolCallId: string; toolName: string } | undefined
const showError = (error: unknown) => {
output.setText(`Error: ${error instanceof Error ? error.message : String(error)}`)
busy = false
pendingApproval = undefined
tui.requestRender()
}
const unsubscribe = session.subscribe(event => {
if (event.type === 'message_update' && event.message.role === 'assistant') {
output.setText(getText(event.message))
} else if (event.type === 'tool_start') {
output.setText(`Running ${event.toolName}...`)
} else if (event.type === 'tool_approval_required') {
pendingApproval = { toolCallId: event.toolCallId, toolName: event.toolName }
output.setText(`Allow ${event.toolName}? Enter y or n.`)
} else if (event.type === 'agent_end') {
busy = false
} else if (event.type === 'error') {
showError(event.error)
return
}
tui.requestRender()
})
editor.onSubmit = value => {
if (pendingApproval) {
const answer = value.trim().toLowerCase()
if (answer !== 'y' && answer !== 'n') {
output.setText(`Allow ${pendingApproval.toolName}? Enter y or n.`)
tui.requestRender()
return
}
const approval = pendingApproval
pendingApproval = undefined
session.respondToToolApproval({
toolCallId: approval.toolCallId,
decision: answer === 'y' ? 'approve' : 'decline',
})
return
}
if (busy || !value.trim()) return
busy = true
output.setText('Thinking...')
tui.requestRender()
void session.sendMessage({ content: value.trim() }).catch(showError)
}
tui.addChild(new Text('My Coding Agent', 1, 0))
tui.addChild(output)
tui.addChild(editor)
tui.setFocus(editor)
let stopPromise: Promise<void> | undefined
let removeInputListener = () => {}
const stop = () => {
stopPromise ??= (async () => {
process.off('SIGINT', handleExit)
removeInputListener()
session.abort()
unsubscribe()
tui.stop()
await controller.destroy()
})()
return stopPromise
}
const handleExit = () => {
void stop().catch(error => {
console.error(error)
process.exitCode = 1
})
}
removeInputListener = tui.addInputListener(data => {
if (!matchesKey(data, 'ctrl+c')) return
handleExit()
return { consume: true }
})
process.once('SIGINT', handleExit)
tui.start()
return { stop }
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
await startCodingAgentTui()
}
選用的 Terminal 參數可支援自動化測試,正常執行時則使用 ProcessTerminal。UI 刻意只 render 最新回應;Session 仍會保留對話 context,以供後續 prompt 使用。
執行 coding agent「執行 coding agent」的直接連結
從專案根目錄啟動終端機應用程式,讓 process.cwd() 指向你要 Agent 使用的專案:
- npm
- pnpm
- Yarn
- Bun
npx tsx src/coding-agent-tui.ts
pnpm dlx tsx src/coding-agent-tui.ts
yarn dlx tsx src/coding-agent-tui.ts
bun x tsx src/coding-agent-tui.ts
輸入以下 prompt:
Inspect package.json and report the package name and available scripts. Do not modify files.
當 controller 詢問是否允許 mastra_workspace_read_file 時,輸入 y。Agent 會讀取 package.json 並回報找到的內容。Model 的措辭可能有所不同,但回應應包含 package 名稱及其 scripts,且不變更檔案。
按下 Ctrl+C 關閉應用程式並銷毀 controller。
後續步驟「後續步驟」的直接連結
你可以在此基礎上進一步擴充:
- 新增 storage,以保存及恢復 controller session
- 新增更多具有不同 instructions 與 Workspace Tool allowlist 的 mode
- 將最新回應元件替換為能 render Tool 呼叫與結果的逐字稿
- 在接受不可信任的 prompt 或散布應用程式前,加入 Sandbox 隔離
深入瞭解: