oss-sync 1602d52510
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (2ef0fa6)
2026-06-17 05:23:28 +00:00

191 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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> = {
// updateuseragents.md にまとめる
readuseragents: 'updateuseragents',
// 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',
xtimeline: 'xsearch',
// youtube.ts をまとめる
searchyoutube: 'getyoutubetranscript',
// maps.ts をまとめる
getdirections: 'searchplaces',
reversegeocode: 'searchplaces',
// office.ts をまとめる
readpdf: 'office',
readexcel: 'office',
readdocx: 'office',
readpptx: 'office',
readmsg: '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',
sshconsolerun: '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 };
}
}