> Discover all available pages from the documentation index: https://mastra.zisheng.pro/ja/llms.txt # Coding Agent を構築する このガイドでは、Mastra Code、Claude Code、Codex と同じカテゴリに属する小規模な Coding Agent アプリケーションを構築します。`buildBasePrompt()` と `createCodingAgent()` で Coding Agent を作成し、対話型セッションと Tool の承認を処理するために `AgentController` でラップして、pi-tui で構築したターミナル UI から Controller を実行します。 以下の動画では、このガイドで構築する Coding Agent の動作を確認できます。 ## 前提条件 - Node.js `v22.19.0` 以降がインストールされていること - サポートされている[モデル Provider](https://mastra.zisheng.pro/ja/models) の API キー - 既存の Mastra プロジェクト。必要な場合は、[インストールガイド](https://mastra.zisheng.pro/ja/guides/getting-started/quickstart)に従ってください。 ## ターミナル用の依存関係をインストールする [pi-tui](https://github.com/earendil-works/pi/tree/main/packages/tui) と `tsx` をインストールします。 **npm**: ```bash npm install @earendil-works/pi-tui npm install --save-dev tsx ``` **pnpm**: ```bash pnpm add @earendil-works/pi-tui pnpm add --save-dev tsx ``` **Yarn**: ```bash yarn add @earendil-works/pi-tui yarn add --dev tsx ``` **Bun**: ```bash bun add @earendil-works/pi-tui bun add --dev tsx ``` pi-tui はターミナルレンダラーと入力エディターを提供します。`tsx` は TypeScript のエントリーポイントを直接実行します。 ## Coding Agent を作成する `src/mastra/agents/coding-agent.ts` を作成します。プロンプトでは現在のプロジェクトを説明し、プロンプト内の汎用 Tool 名をデフォルトの Workspace が提供する Tool に対応付けます。 ```typescript 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 は分離を行わず、ホスト上でコマンドを直接実行します。そのため、`basePath` はオペレーティングシステム上のセキュリティ境界ではありません。この例ではターミナルで Tool 呼び出しごとの承認を追加しますが、信頼できるローカルプロジェクトに対してのみ実行してください。 ## Coding Agent を登録する 返された Agent を、ほかの Mastra Agent と同じように `src/mastra/index.ts` へ登録します。登録すると、基になる Agent を Studio および Mastra サーバーからも利用できるようになります。 ```typescript import { Mastra } from '@mastra/core/mastra' import { codingAgent } from './agents/coding-agent' export const mastra = new Mastra({ agents: { codingAgent }, }) ``` ## Coding Agent をテストする ターミナルインターフェースを追加する前に、Studio で基になる Agent を検証します。開発サーバーを起動すると作業ディレクトリは `src/mastra/public` になるため、Agent が確認できる機密情報を含まないファイルをそこへ追加します。 ```md # Project notes Name: Acme support portal Status: In development Owner: Platform team ``` 開発サーバーを起動します。 **npm**: ```bash npm run dev ``` **pnpm**: ```bash pnpm run dev ``` **Yarn**: ```bash yarn dev ``` **Bun**: ```bash bun run dev ``` [Studio](https://mastra.zisheng.pro/ja/docs/studio/overview) を開いて **Coding Agent** を選択し、次を入力します。 ```text Inspect project-notes.md and report the project name, status, and owner. Do not modify files. ``` レスポンスでは Acme support portal を特定し、開発中であることを説明します。また、Platform team がプロジェクトを所有していることを報告し、ファイルは変更されないはずです。モデルによって表現は異なる場合があります。 ## Agent Controller を作成する `src/mastra/coding-agent-controller.ts` を作成します。Controller は対話型セッションを管理し、UI イベントを公開して、承認が必要な Workspace Tool を一時停止します。 ```typescript 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 } } ``` この例ではモードを 1 つだけ使用し、Controller に追加される組み込み Tool を無効化して、入門用 UI が Workspace での実行に集中できるようにしています。この簡略化したセットアップはチュートリアル向けです。本番アプリケーションでは、製品に必要な組み込み Tool を有効化し、それぞれの UI フローを実装してください。`ask_user` や `submit_plan` などの対話型 Tool は、インターフェースから再開されるまで一時停止します。一方、タスク Tool と Subagent Tool には固有のライフサイクルイベントがあります。[Tool の承認と一時停止](https://mastra.zisheng.pro/ja/docs/harness/agent-controller)を参照してください。 この例では Storage も省略しているため、会話は現在のプロセスが実行されている間だけ保持されます。後から Storage を追加すれば、セッションを再開できます。 ## ターミナル UI を構築する `src/coding-agent-tui.ts` を作成します。UI は Assistant メッセージの更新と Tool の動作を表示します。また、Workspace Tool を呼び出すたびに、許可するか拒否するかをユーザーに確認します。 ```typescript 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 | 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 は意図的に最新のレスポンスだけをレンダリングします。後続のプロンプトに備え、`Session` は引き続き会話のコンテキストを保持します。 ## Coding Agent を実行する `process.cwd()` が Agent に使用させるプロジェクトを指すように、プロジェクトのルートからターミナルアプリケーションを起動します。 **npm**: ```bash npx tsx src/coding-agent-tui.ts ``` **pnpm**: ```bash pnpm dlx tsx src/coding-agent-tui.ts ``` **Yarn**: ```bash yarn dlx tsx src/coding-agent-tui.ts ``` **Bun**: ```bash bun x tsx src/coding-agent-tui.ts ``` 次のプロンプトを入力します。 ```text Inspect package.json and report the package name and available scripts. Do not modify files. ``` Controller から `mastra_workspace_read_file` の許可を求められたら、`y` を入力します。Agent は `package.json` を読み取り、見つけた内容を報告します。モデルによって表現は異なりますが、ファイルを変更せずにパッケージ名とスクリプトがレスポンスに含まれます。 **Ctrl+C** を押すとアプリケーションが終了し、Controller が破棄されます。 ## 次のステップ この基盤は、次のように拡張できます。 - Storage を追加して Controller セッションを永続化し、再開できるようにする - 指示と Workspace Tool の許可リストが異なるモードを追加する - 最新レスポンスだけを表示するコンポーネントを、Tool の呼び出しと結果も表示するトランスクリプトに置き換える - 信頼できないプロンプトを受け入れたり、アプリケーションを配布したりする前に、Sandbox の分離を追加する 詳細: - [`createCodingAgent()` リファレンス](https://mastra.zisheng.pro/ja/reference/coding-agent/create-coding-agent) - [`buildBasePrompt()` リファレンス](https://mastra.zisheng.pro/ja/reference/coding-agent/build-base-prompt) - [AgentController の概要](https://mastra.zisheng.pro/ja/docs/harness/agent-controller) - [`AgentController` リファレンス](https://mastra.zisheng.pro/ja/reference/agent-controller/agent-controller-class) - [Workspace の概要](https://mastra.zisheng.pro/ja/docs/workspace/overview)