feat: initial public release (MAESTRO)
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
// docs.ts — ツール詳細ドキュメント参照用ツール
|
||||
//
|
||||
// リポジトリ内 docs/tools/{name}.md を読み込んで返す。
|
||||
// ワークスペース外の固定パスから読むため、Read ツールでは到達できない。
|
||||
// Tool description にこのツールへのポインタを書いておくと、
|
||||
// 詳細な使い方を必要に応じてエージェントが取得できる。
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { ToolDef } from '../../llm/openai-compat.js';
|
||||
import type { ToolContext, ToolResult } from './core.js';
|
||||
import { logger } from '../../logger.js';
|
||||
|
||||
type McpToolLookup = (serverId: string, toolName: string) => { description: string | null; input_schema: string | null } | null;
|
||||
let _mcpLookup: McpToolLookup | null = null;
|
||||
export function setMcpToolLookup(fn: McpToolLookup | null): void {
|
||||
_mcpLookup = fn;
|
||||
}
|
||||
|
||||
// dist/engine/tools/docs.js または src/engine/tools/docs.ts から
|
||||
// リポジトリルートを解決し、docs/tools/ を指す
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
|
||||
const DOCS_DIR = path.join(REPO_ROOT, 'docs', 'tools');
|
||||
|
||||
// 関連ツールが同じ doc を参照できるようエイリアスを定義
|
||||
// キー・値ともに小文字
|
||||
const TOOL_DOC_ALIASES: Record<string, string> = {
|
||||
// checklist.md にまとめる
|
||||
createchecklist: 'checklist',
|
||||
checkitem: 'checklist',
|
||||
getchecklist: 'checklist',
|
||||
// searchknowledge.md にまとめる
|
||||
listnamespaces: 'searchknowledge',
|
||||
listdocuments: 'searchknowledge',
|
||||
ingestdocument: 'searchknowledge',
|
||||
ingeststatus: 'searchknowledge',
|
||||
// x.ts ツールをまとめる
|
||||
xuserposts: 'xsearch',
|
||||
xpostdetail: 'xsearch',
|
||||
xfetchcardmedia: 'xsearch',
|
||||
// youtube.ts をまとめる
|
||||
searchyoutube: 'getyoutubetranscript',
|
||||
// maps.ts をまとめる
|
||||
getdirections: 'searchplaces',
|
||||
reversegeocode: 'searchplaces',
|
||||
// office.ts をまとめる
|
||||
readpdf: 'office',
|
||||
readexcel: 'office',
|
||||
readdocx: 'office',
|
||||
readpptx: 'office',
|
||||
pdftoimages: 'office',
|
||||
splitexcelsheets: 'office',
|
||||
splitdocxsections: 'office',
|
||||
// pieces.ts をまとめる
|
||||
getpiece: 'listpieces',
|
||||
createpiece: 'listpieces',
|
||||
updatepiece: 'listpieces',
|
||||
// ms-learn.ts をまとめる
|
||||
fetchmicrosoftlearn: 'searchmicrosoftlearn',
|
||||
searchmicrosoftlearncache: 'searchmicrosoftlearn',
|
||||
refreshmicrosoftlearncache: 'searchmicrosoftlearn',
|
||||
// browser.ts: InteractiveBrowse / BrowseWithSession は browseweb.md にまとめる
|
||||
interactivebrowse: 'browseweb',
|
||||
browsewithsession: 'browseweb',
|
||||
// ssh.ts をまとめる
|
||||
sshexec: 'ssh-tools',
|
||||
sshupload: 'ssh-tools',
|
||||
sshdownload: 'ssh-tools',
|
||||
sshlistconnections: 'ssh-tools',
|
||||
// ssh-console.ts をまとめる
|
||||
sshconsoleensure: 'ssh-console-tools',
|
||||
sshconsolesend: 'ssh-console-tools',
|
||||
sshconsolesnapshot: 'ssh-console-tools',
|
||||
// slide.ts をまとめる
|
||||
settheme: 'slide',
|
||||
addslide: 'slide',
|
||||
buildpptx: 'slide',
|
||||
resetslides: 'slide',
|
||||
// notes.ts をまとめる
|
||||
searchnotes: 'notes',
|
||||
readnote: 'notes',
|
||||
writenote: 'notes',
|
||||
};
|
||||
|
||||
const READ_TOOL_DOC_DEF: ToolDef = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'ReadToolDoc',
|
||||
description:
|
||||
'ツールの詳細な使い方ドキュメントを読み込む。各ツールの description は概要のみで、詳細な手順や例が必要なときはこれを呼ぶ。'
|
||||
+ 'docs/tools/{name}.md(リポジトリ内固定パス)を参照する。',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: '読みたいツール名(例: "BrowseWeb", "SearchKnowledge")',
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const TOOL_DEFS: Record<string, ToolDef> = {
|
||||
ReadToolDoc: READ_TOOL_DOC_DEF,
|
||||
};
|
||||
|
||||
export async function executeTool(
|
||||
name: string,
|
||||
input: Record<string, unknown>,
|
||||
_ctx: ToolContext,
|
||||
): Promise<ToolResult | null> {
|
||||
if (name !== 'ReadToolDoc') return null;
|
||||
|
||||
const toolName = input['name'] as string | undefined;
|
||||
if (!toolName || typeof toolName !== 'string') {
|
||||
return { output: 'ReadToolDoc error: name パラメータが必要です', isError: true };
|
||||
}
|
||||
|
||||
// MCP ツール名 (mcp__<serverId>__<toolName>) の場合はキャッシュから返す
|
||||
if (toolName.startsWith('mcp__')) {
|
||||
// Lazy-import to avoid a hard dependency on mcp module at module-load time.
|
||||
const { parseToolName } = await import('../../mcp/tool-adapter.js');
|
||||
const parsed = parseToolName(toolName);
|
||||
if (!parsed) {
|
||||
return { output: `ReadToolDoc: 不正な MCP ツール名 "${toolName}"`, isError: true };
|
||||
}
|
||||
if (!_mcpLookup) {
|
||||
return { output: 'ReadToolDoc: MCP サブシステムが初期化されていません', isError: true };
|
||||
}
|
||||
const row = _mcpLookup(parsed.serverId, parsed.toolName);
|
||||
if (!row) {
|
||||
return { output: `ReadToolDoc: ${toolName} のキャッシュ情報がありません`, isError: false };
|
||||
}
|
||||
let schemaBlock = '';
|
||||
if (row.input_schema) {
|
||||
try {
|
||||
const schema = JSON.parse(row.input_schema);
|
||||
schemaBlock = `\n\n## Input schema\n\n\`\`\`json\n${JSON.stringify(schema, null, 2)}\n\`\`\``;
|
||||
} catch {
|
||||
schemaBlock = `\n\n## Input schema (raw)\n\n\`\`\`\n${row.input_schema}\n\`\`\``;
|
||||
}
|
||||
}
|
||||
return {
|
||||
output: `# ${toolName}\n\n${row.description ?? '(no description)'}${schemaBlock}`,
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
// パストラバーサル防止: 英数字とハイフン・アンダースコアのみ許可
|
||||
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(toolName)) {
|
||||
return { output: `ReadToolDoc error: 不正なツール名 "${toolName}"`, isError: true };
|
||||
}
|
||||
|
||||
const lowerName = toolName.toLowerCase();
|
||||
const resolvedName = TOOL_DOC_ALIASES[lowerName] ?? lowerName;
|
||||
const docPath = path.join(DOCS_DIR, `${resolvedName}.md`);
|
||||
try {
|
||||
const content = await fs.promises.readFile(docPath, 'utf-8');
|
||||
return { output: content, isError: false };
|
||||
} catch (e: any) {
|
||||
if (e.code === 'ENOENT') {
|
||||
// 利用可能なドキュメント一覧を返す
|
||||
try {
|
||||
const files = await fs.promises.readdir(DOCS_DIR);
|
||||
const available = files
|
||||
.filter((f) => f.endsWith('.md'))
|
||||
.map((f) => f.replace(/\.md$/, ''))
|
||||
.sort();
|
||||
return {
|
||||
output: `ReadToolDoc: "${toolName}" のドキュメントは存在しません。\n利用可能なドキュメント:\n${available.map((n) => `- ${n}`).join('\n')}`,
|
||||
isError: true,
|
||||
};
|
||||
} catch {
|
||||
return { output: `ReadToolDoc: "${toolName}" のドキュメントは存在しません。`, isError: true };
|
||||
}
|
||||
}
|
||||
logger.warn(`[ReadToolDoc] failed to read ${docPath}: ${e.message}`);
|
||||
return { output: `ReadToolDoc error: ${e.message}`, isError: true };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user