Files
video-create/web/server/agent/tools.ts
sion123 001dbde9f4 feat(web): integrate Claude LLM streaming with markdown rendering
- Add Anthropic SDK with DeepSeek-compatible API config
- Streaming tool-use loop in WebSocket chat handler
- GitHub-style markdown rendering with markdown-it
- Tool status indicators and thinking states in chat UI
- Fix Tailwind content path and CSS border utility

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 03:22:15 +08:00

140 lines
5.7 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.
import { spawn, execSync } from 'child_process';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
const PIPELINE_SCRIPT = path.join(PROJECT_ROOT, '.claude', 'skills', 'video-from-script', 'scripts', 'pipeline.js');
export interface ToolDefinition {
name: string;
description: string;
input_schema: {
type: 'object';
properties: Record<string, { type: string; description: string }>;
required?: string[];
};
execute: (params: Record<string, unknown>) => Promise<string>;
}
export const tools: ToolDefinition[] = [
{
name: 'list_accounts',
description: '列出所有可用账号,返回每个账号的名称、描述、生图模型和视频模型',
input_schema: {
type: 'object',
properties: {},
required: [],
},
execute: async () => {
const accountsDir = path.join(PROJECT_ROOT, 'accounts');
const dirs = fs.readdirSync(accountsDir, { withFileTypes: true })
.filter((d) => d.isDirectory() && !d.name.startsWith('_') && !d.name.startsWith('.'))
.map((d) => {
const configPath = path.join(accountsDir, d.name, 'account.json');
if (fs.existsSync(configPath)) {
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
return `${d.name} - ${cfg.description || '无描述'} (生图:${cfg.imageModel} 视频:${cfg.videoModel} 画幅:${cfg.defaultFormat})`;
}
return d.name;
});
return dirs.join('\n') || '暂无账号';
},
},
{
name: 'create_account',
description: '创建新的短视频账号。需要提供账号ID、名称和描述。创建后可在 accounts/ 目录下找到配置。',
input_schema: {
type: 'object',
properties: {
id: { type: 'string', description: '账号唯一标识,英文小写,如 military-account' },
name: { type: 'string', description: '账号显示名称,中文,如 军事账号' },
desc: { type: 'string', description: '账号描述,说明视频风格和主题' },
imageModel: { type: 'string', description: '生图模型: gemini, mj, gpt, kling' },
videoModel: { type: 'string', description: '视频模型: veo3-fast, veo3-fast-frames, kling, grok' },
format: { type: 'string', description: '画幅: 9:16 (竖屏), 16:9 (横屏), 1:1 (方形)' },
},
required: ['id', 'name'],
},
execute: async (params) => {
const { id, name, desc, imageModel, videoModel, format } = params as Record<string, string>;
const cmd = [
`node "${PIPELINE_SCRIPT}" create-account`,
`--id "${id}"`,
`--name "${name}"`,
`--desc "${desc || ''}"`,
`--video-model ${videoModel || 'veo3-fast'}`,
imageModel ? `--image-model ${imageModel}` : '',
format ? `--format ${format}` : '',
].filter(Boolean).join(' ');
const result = execSync(cmd, { cwd: PROJECT_ROOT, encoding: 'utf-8' });
return `账号「${name}」创建成功。\n${result}`;
},
},
{
name: 'pipeline_status',
description: '查看指定 manifest 的 pipeline 执行进度和各阶段状态',
input_schema: {
type: 'object',
properties: {
manifest: { type: 'string', description: 'manifest.json 的绝对路径' },
},
required: ['manifest'],
},
execute: async (params) => {
const { manifest } = params as { manifest: string };
const result = execSync(`node "${PIPELINE_SCRIPT}" status --manifest "${manifest}"`, {
cwd: PROJECT_ROOT, encoding: 'utf-8',
});
return result;
},
},
{
name: 'run_pipeline_phase',
description: '执行视频创作 pipeline 的指定阶段。阶段顺序: images(生图) → upload(上传) → videos(生视频) → tts(配音) → assemble(成片组装)。执行前需确认 manifest.json 已存在。',
input_schema: {
type: 'object',
properties: {
manifest: { type: 'string', description: 'manifest.json 的绝对路径' },
phase: { type: 'string', description: '要执行的阶段: images, upload, videos, tts, assemble。多个阶段用逗号分隔如 images,upload' },
},
required: ['manifest', 'phase'],
},
execute: async (params) => {
const { manifest, phase } = params as { manifest: string; phase: string };
return new Promise((resolve, reject) => {
const proc = spawn('node', [PIPELINE_SCRIPT, 'run', '--manifest', manifest, '--phase', phase], {
cwd: PROJECT_ROOT,
env: { ...process.env },
});
let output = '';
proc.stdout.on('data', (d: Buffer) => { output += d.toString(); });
proc.stderr.on('data', (d: Buffer) => { output += d.toString(); });
proc.on('close', (code) => {
code === 0 ? resolve(output || '执行成功') : reject(new Error(`Pipeline exit code ${code}: ${output.slice(-500)}`));
});
});
},
},
{
name: 'get_account_config',
description: '获取指定账号的完整配置包括模型选择、TTS语音、字幕风格等',
input_schema: {
type: 'object',
properties: {
accountId: { type: 'string', description: '账号ID如 军事账号' },
},
required: ['accountId'],
},
execute: async (params) => {
const { accountId } = params as { accountId: string };
const configPath = path.join(PROJECT_ROOT, 'accounts', accountId, 'account.json');
if (!fs.existsSync(configPath)) return `账号「${accountId}」不存在`;
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
return JSON.stringify(cfg, null, 2);
},
},
];