Files
OpenIsle/bots/reply_bots.ts

132 lines
4.8 KiB
TypeScript
Raw Permalink 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.
// reply_bot.ts
import { Agent, Runner, hostedMcpTool, withTrace } from "@openai/agents";
console.log("✅ Reply bot starting...");
const allowedMcpTools = [
"search",
"reply_to_post",
"reply_to_comment",
"recent_posts",
"get_post",
"list_unread_messages",
"mark_notifications_read",
];
console.log("🛠️ Configured Hosted MCP tools:", allowedMcpTools.join(", "));
// ---- MCP 工具Hosted MCP ----
// 关键点requireApproval 设为 "never",避免卡在人工批准。
const mcp = hostedMcpTool({
serverLabel: "openisle_mcp",
serverUrl: "https://www.open-isle.com/mcp",
allowedTools: allowedMcpTools,
requireApproval: "never",
});
type WorkflowInput = { input_as_text: string };
// 从环境变量读取你的站点鉴权令牌(可选)
const OPENISLE_TOKEN = process.env.OPENISLE_TOKEN ?? "";
console.log(
OPENISLE_TOKEN
? "🔑 OPENISLE_TOKEN detected in environment."
: "🔓 OPENISLE_TOKEN not set; agent will request it if required."
);
// ---- 定义 Agent ----
const openisleBot = new Agent({
name: "OpenIsle Bot",
instructions: [
"You are a helpful and cute assistant for https://www.open-isle.com. Please use plenty of kawaii kaomoji (颜表情), such as (๑˃ᴗ˂)ﻭ, (•̀ω•́)✧, (。•ᴗ-)_♡, (⁎⁍̴̛ᴗ⁍̴̛⁎), etc., in your replies to create a friendly, adorable vibe.",
"Finish tasks end-to-end before replying. If multiple MCP tools are needed, call them sequentially until the task is truly done.",
"When presenting the result, reply in Chinese with a concise, cute summary filled with kaomoji and include any important URLs or IDs.",
OPENISLE_TOKEN
? `If tools require auth, use this token exactly where the tool schema expects it: ${OPENISLE_TOKEN}`
: "If a tool requires auth, ask me to provide OPENISLE_TOKEN via env.",
"After finishing replies, call mark_notifications_read with all processed notification IDs to keep the inbox clean.",
].join("\n"),
tools: [mcp],
model: "gpt-4o",
modelSettings: {
temperature: 0.7,
topP: 1,
maxTokens: 2048,
toolChoice: "auto",
store: true,
},
});
// ---- 入口函数:跑到拿到 finalOutput 为止,然后输出并退出 ----
export const runWorkflow = async (workflow: WorkflowInput) => {
// 强烈建议在外部shell设置 OPENAI_API_KEY
if (!process.env.OPENAI_API_KEY) {
throw new Error("Missing OPENAI_API_KEY");
}
const runner = new Runner({
workflowName: "OpenIsle Bot",
traceMetadata: {
__trace_source__: "agent-builder",
workflow_id: "wf_69003cbd47e08190928745d3c806c0b50d1a01cfae052be8",
},
// 如需完全禁用上报可加tracingDisabled: true
});
return await withTrace("OpenIsle Bot run", async () => {
const preview = workflow.input_as_text.trim();
console.log(
"📝 Received workflow input (preview):",
preview.length > 200 ? `${preview.slice(0, 200)}` : preview
);
// Runner.run 会自动循环执行LLM → 工具 → 直至 finalOutput
console.log("🚦 Starting agent run with maxTurns=16...");
const result = await runner.run(openisleBot, workflow.input_as_text, {
maxTurns: 16, // 允许更复杂任务多轮调用 MCP
// stream: true // 如需边跑边看事件可打开,然后消费流事件
});
console.log("📬 Agent run completed. Result keys:", Object.keys(result));
if (!result.finalOutput) {
// 若没产出最终结果,通常是启用了人工批准/工具失败/达到 maxTurns
throw new Error("Agent result is undefined (no final output).");
}
const openisleBotResult = { output_text: String(result.finalOutput) };
console.log(
"🤖 Agent result (length=%d):\n%s",
openisleBotResult.output_text.length,
openisleBotResult.output_text
);
return openisleBotResult;
});
};
// ---- CLI 运行(示范)----
if (require.main === module) {
(async () => {
try {
const query = `
【AUTO】无需确认自动处理所有未读的提及与评论
1调用 list_unread_messages
2依次处理每条“提及/评论”:如需上下文则使用 get_post 获取,生成简明中文回复;如有 commentId 则用 reply_to_comment否则用 reply_to_post
3跳过关注和系统事件
4保证幂等性如该贴最后一条是你自己发的回复则跳过
5调用 mark_notifications_read传入本次已处理的通知 ID 清理已读;
6最多只处理最新10条结束时仅输出简要摘要包含URL或ID
`;
console.log("🔍 Running workflow...");
await runWorkflow({ input_as_text: query });
process.exit(0);
} catch (err: any) {
console.error("❌ Agent failed:", err?.stack || err);
process.exit(1);
}
})();
}