建立編程 Agent
本指南會教你建立一個小型編程 Agent 應用程式,類型與 Mastra Code、Claude Code 或 Codex 相同。你會使用 buildBasePrompt() 和 createCodingAgent() 建立編程 Agent,再以 AgentController 包裝它,以支援互動式工作階段及 Tool 核准,最後在使用 pi-tui 建立的終端使用者介面中執行控制器。
以下影片展示你將建立的編程 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 提供終端渲染器及輸入編輯器。tsx 會直接執行 TypeScript 入口點。
建立編程 Agent建立編程 Agent 的直接連結
建立 src/mastra/agents/coding-agent.ts。提示會描述目前項目,並將提示中的通用 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() 會提供本機檔案系統及 Sandbox Workspace,並包含從暫時性 Provider 錯誤中恢復的預設設定。basePath 會限制檔案系統 Tool 的作用範圍,並設定命令的初始工作目錄。你傳遞至工廠函數的任何設定都會優先於其預設值。
預設本機 Sandbox 會直接在主機上執行命令,並不提供隔離,因此 basePath 並非作業系統層面的安全邊界。此範例會在終端為每次 Tool 呼叫加入核准步驟,但你仍只應針對可信任的本機項目執行它。
註冊編程 Agent註冊編程 Agent 的直接連結
與任何其他 Mastra Agent 一樣,在 src/mastra/index.ts 註冊傳回的 Agent。註冊後,底層 Agent 亦可在 Studio 及透過 Mastra 伺服器使用。
import { Mastra } from '@mastra/core/mastra'
import { codingAgent } from './agents/coding-agent'
export const mastra = new Mastra({
agents: { codingAgent },
})
測試編程 Agent測試編程 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 支援入口網站及說明其開發狀態,亦應指出項目由 Platform 團隊負責,並保持檔案不變。模型的措辭可能有所不同。
建立 Agent 控制器建立 Agent 控制器 的直接連結
建立 src/mastra/coding-agent-controller.ts。控制器會管理互動式工作階段、公開使用者介面事件,並暫停 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 }
}
此範例只使用一種模式,並停用控制器的額外內置 Tool,讓入門使用者介面集中處理 Workspace 執行。這個簡化設定專為本教學而設。在正式應用程式中,請啟用產品所需的內置 Tool 並實作其使用者介面流程:ask_user 和 submit_plan 等互動式 Tool 會暫停,直至介面恢復執行;任務及子代理 Tool 則各有自己的生命週期事件。詳情請參閱 Tool 核准及暫停。
此範例亦省略儲存空間,因此對話只會維持至目前程序結束。如要恢復工作階段,可在之後加入儲存空間。
建立終端使用者介面建立終端使用者介面 的直接連結
建立 src/coding-agent-tui.ts。使用者介面會渲染助理訊息更新並顯示 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。使用者介面刻意只渲染最新回應。Session 仍會保留對話內容,以供後續提示使用。
執行編程 Agent執行編程 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
輸入以下提示:
Inspect package.json and report the package name and available scripts. Do not modify files.
當控制器詢問是否允許 mastra_workspace_read_file 時,輸入 y。Agent 會讀取 package.json 並報告當中內容。模型措辭會有所不同,但回應應包括依賴套件名稱及其指令碼,而且不會更改檔案。
按下 Ctrl+C 關閉應用程式並銷毀控制器。
後續步驟後續步驟 的直接連結
你可以透過以下方式擴充這個基礎:
- 加入儲存空間,以保存及恢復控制器工作階段
- 加入更多具有不同指示及 Workspace Tool 允許清單的模式
- 將最新回應元件換成可渲染 Tool 呼叫及結果的對話記錄
- 在接受不可信任的提示或發佈應用程式前加入 Sandbox 隔離
深入了解: