Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a9b907f
feat: 原生函数调用 + 串行批量工具执行
baicaizhale Aug 8, 2026
332b379
fix: 修改plugin.yml的版本号
baicaizhale Aug 8, 2026
8e842dc
fix: 修改config中的部分注释
baicaizhale Aug 8, 2026
e084314
fix: API 报错时补充控制台诊断日志
baicaizhale Aug 8, 2026
c5be2ff
fix: 工具 schema 缺 type=object 包裹层,DeepSeek 拒绝函数调用
baicaizhale Aug 8, 2026
22b3d5a
fix: ask/todo 嵌套数组 schema 非法,DeepSeek 拒收导致草方块未执行
baicaizhale Aug 8, 2026
7ba6040
feat: native_tool_calling 开启时放开"一次只能一个调用"约束
baicaizhale Aug 8, 2026
1ea58a7
fix: 退出 CLI 后深度重载不应自动进入 CLI
baicaizhale Aug 8, 2026
a4d93d7
feat: 禁用重载后自动恢复会话,改为 /cli resume 手动恢复
baicaizhale Aug 8, 2026
434d7be
feat: 关闭提示显示具体 /cli resume <uuid>,整条命令可点击执行
baicaizhale Aug 8, 2026
302848c
style: /cli resume 恢复命令改为白色显示
baicaizhale Aug 8, 2026
154f826
fix: 关闭提示点击执行 resume_confirm 而非 resume
baicaizhale Aug 8, 2026
02475f4
feat: NORMAL/SMART 模式多 run 串行批次执行
baicaizhale Aug 8, 2026
c93275a
feat: 命令输出捕获改为静默驱动窗口,替代固定延迟
baicaizhale Aug 8, 2026
8b83006
fix: todo 兼容 native 的 todos 包裹对象 + 工具错误不再展示给玩家
baicaizhale Aug 8, 2026
beadbfa
refactor: tools API 描述改为英文,减少 AI token 消耗
baicaizhale Aug 8, 2026
0e86665
chore: 清理死代码、未使用 import 与冗余提示词,默认开启 native tool calling
baicaizhale Aug 8, 2026
dac83ec
fix: 原生函数调用显式声明 parallel_tool_calls + tool_choice
baicaizhale Aug 8, 2026
646c39b
fix: 文本协议多工具支持(#tool 批次串行执行)
baicaizhale Aug 9, 2026
5b44291
fix: 原生模式下删除 #tool 文本语法列表,减少 gemma 文本协议回退
baicaizhale Aug 9, 2026
f071c98
fix: 流式 tool_calls 按 id 归并,修复 CF gemma 并行调用共享 index 被合并
baicaizhale Aug 9, 2026
e03a420
feat: #run 命令存在性校验兜底,疑似斜杠错误拦截教育模型
baicaizhale Aug 9, 2026
d7459d2
修改提示词
baicaizhale Aug 9, 2026
dc00aae
fix: 批次超时基准独立化 + /cli stop 清理批次状态
baicaizhale Aug 10, 2026
87a96ce
test: 扩大可批工具集测试(ask/edit/write/mcp/记忆类)+ 更新为 isRiskyRunCall 语义
baicaizhale Aug 10, 2026
c28a1c0
fix: 工具执行异常反馈兜底,批次统计去重复计数
baicaizhale Aug 10, 2026
82fdbae
fix: 待同意协议玩家无法 /cli 退出(exitCLI 清理 pendingAgreementPlayers)
baicaizhale Aug 10, 2026
e0a95fa
何意味
baicaizhale Aug 10, 2026
a6c92fb
fix: 原生工具调用兼容与配置热重载修复
baicaizhale Aug 11, 2026
6ee5c33
fix: CLI 会话保存与压缩输入处理
baicaizhale Aug 11, 2026
d150050
feat: 文件工具 JSON 行格式 + 主对话温度可配置 + 会话原子写入
baicaizhale Aug 11, 2026
aed422d
fix: YOLO 模式 #edit/#write 残留 pendingCommand 导致后续消息被"请确认"吞掉
baicaizhale Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,4 @@ buildNumber.properties
run/
.opencode
.kilo/
.zcode/
20 changes: 2 additions & 18 deletions src/main/java/org/YanPl/FancyHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,31 +200,15 @@ public void onEnable() {
// 尝试同步命令,修复热重载后的 Brigadier 缓存问题
syncCommands();

// 检查在线玩家是否有预加载的会话,如果有则自动进入CLI模式
checkOnlinePlayersForPreloadedSessions();
// 已禁用:插件重载/重启后不再自动恢复会话,改为玩家手动 /cli resume。
// checkOnlinePlayersForPreloadedSessions();
} catch (Throwable e) {
getLogger().severe("FancyHelper 启动失败: " + e.getMessage());
e.printStackTrace();
setEnabled(false);
}
}

/**
* 检查在线玩家是否有预加载的会话,如果有则自动进入CLI模式
*/
private void checkOnlinePlayersForPreloadedSessions() {
getServer().getScheduler().runTaskLater(this, () -> {
for (org.bukkit.entity.Player player : getServer().getOnlinePlayers()) {
if (cliManager.hasPreloadedSession(player.getUniqueId())) {
if (getConfigManager().isDebug()) {
getLogger().info("[FancyHelper] 玩家 " + player.getName() + " 有预加载的会话,自动进入CLI模式");
}
cliManager.enterCLI(player, true);
}
}
}, 20L); // 延迟1秒执行,确保插件完全加载
}

// 下面一些代码只是为了清理旧插件防止干扰,没有任何恶意
private void cleanOldPluginFiles() {
java.io.File pluginsDir = getDataFolder().getParentFile();
Expand Down
175 changes: 158 additions & 17 deletions src/main/java/org/YanPl/api/LLMClient.java

Large diffs are not rendered by default.

63 changes: 59 additions & 4 deletions src/main/java/org/YanPl/api/ResponseParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.YanPl.model.AIResponse;
import org.YanPl.model.NativeToolCall;

import java.util.ArrayList;
import java.util.List;

/**
* AI API 响应解析器
Expand All @@ -21,6 +25,7 @@ public class ResponseParser {
public AIResponse parseResponse(JsonObject responseJson) {
String textContent = null;
String thoughtContent = null;
List<NativeToolCall> toolCalls = null;
long promptTokens = 0;
long completionTokens = 0;

Expand All @@ -45,20 +50,29 @@ else if (responseJson.has("result") && responseJson.get("result").isJsonObject()
if (openAiContent != null) {
textContent = openAiContent.text;
thoughtContent = openAiContent.thought;
if (openAiContent.toolCalls != null) {
toolCalls = openAiContent.toolCalls;
}
}

// 2. 尝试解析 Cloudflare 原生 output 格式
ParsedContent cfOutputContent = parseCloudFlareOutputFormat(responseJson);
if (cfOutputContent != null) {
if (textContent == null) textContent = cfOutputContent.text;
if (thoughtContent == null) thoughtContent = cfOutputContent.thought;
if (toolCalls == null && cfOutputContent.toolCalls != null) {
toolCalls = cfOutputContent.toolCalls;
}
}

// 3. 尝试解析 Cloudflare 原生 result 格式
ParsedContent cfResultContent = parseCloudFlareResultFormat(responseJson);
if (cfResultContent != null) {
if (textContent == null) textContent = cfResultContent.text;
if (thoughtContent == null) thoughtContent = cfResultContent.thought;
if (toolCalls == null && cfResultContent.toolCalls != null) {
toolCalls = cfResultContent.toolCalls;
}
}

// 检查 finish_reason 字段
Expand All @@ -68,19 +82,23 @@ else if (responseJson.has("result") && responseJson.get("result").isJsonObject()
// 处理 finish_reason 为 stop 但 content 为空的情况(可能是模型错误或内容过滤)
if ("stop".equals(finishReason) && textContent == null) {
// API 返回了 stop 但没有内容,返回空字符串而不是 null
return new AIResponse("", thoughtContent, promptTokens, completionTokens, false);
return new AIResponse("", thoughtContent, promptTokens, completionTokens, false, toolCalls);
}

if (isTruncated && textContent == null) {
// 当 finish_reason 为 length 且 content 为 null 时,返回空内容但标记为截断
return new AIResponse("", thoughtContent, promptTokens, completionTokens, true);
return new AIResponse("", thoughtContent, promptTokens, completionTokens, true, toolCalls);
} else if (isTruncated && textContent != null) {
// 当 finish_reason 为 length 但有内容时,标记为截断
return new AIResponse(textContent, thoughtContent, promptTokens, completionTokens, true);
return new AIResponse(textContent, thoughtContent, promptTokens, completionTokens, true, toolCalls);
}

if (textContent != null) {
return new AIResponse(textContent, thoughtContent, promptTokens, completionTokens);
return new AIResponse(textContent, thoughtContent, promptTokens, completionTokens, false, toolCalls);
}
// 只有原生函数调用、无正文内容(纯工具调用响应)也返回非 null,携带 toolCalls
if (toolCalls != null && !toolCalls.isEmpty()) {
return new AIResponse("", thoughtContent, promptTokens, completionTokens, false, toolCalls);
}
return null;
}
Expand Down Expand Up @@ -150,6 +168,28 @@ private ParsedContent parseOpenAiFormat(JsonObject responseJson) {
}
}

// 解析原生函数调用(OpenAI 格式 message.tool_calls)
if (message.has("tool_calls") && message.get("tool_calls").isJsonArray()) {
JsonArray toolCalls = message.getAsJsonArray("tool_calls");
List<NativeToolCall> calls = new ArrayList<>();
for (int i = 0; i < toolCalls.size(); i++) {
JsonElement el = toolCalls.get(i);
if (!el.isJsonObject()) continue;
JsonObject tc = el.getAsJsonObject();
String id = tc.has("id") && !tc.get("id").isJsonNull() ? tc.get("id").getAsString() : null;
if (!tc.has("function") || !tc.get("function").isJsonObject()) continue;
JsonObject fn = tc.getAsJsonObject("function");
String name = fn.has("name") && !fn.get("name").isJsonNull() ? fn.get("name").getAsString() : null;
String args = fn.has("arguments") && !fn.get("arguments").isJsonNull() ? fn.get("arguments").getAsString() : null;
if (name != null) {
calls.add(new NativeToolCall(id, name, args == null ? "" : args));
}
}
if (!calls.isEmpty()) {
content.toolCalls = calls;
}
}

return content;
}

Expand Down Expand Up @@ -199,6 +239,20 @@ private void parseMessageItem(JsonObject item, ParsedContent content) {
if (!contentObj.get("text").isJsonNull()) {
content.thought = contentObj.get("text").getAsString();
}
} else if ("function_call".equals(type)) {
// Cloudflare Responses API 的原生函数调用项
String name = contentObj.has("name") && !contentObj.get("name").isJsonNull()
? contentObj.get("name").getAsString() : null;
String args = contentObj.has("arguments") && !contentObj.get("arguments").isJsonNull()
? contentObj.get("arguments").getAsString() : null;
String callId = contentObj.has("call_id") && !contentObj.get("call_id").isJsonNull()
? contentObj.get("call_id").getAsString() : null;
if (name != null) {
if (content.toolCalls == null) {
content.toolCalls = new ArrayList<>();
}
content.toolCalls.add(new NativeToolCall(callId, name, args == null ? "" : args));
}
}
}
}
Expand Down Expand Up @@ -293,5 +347,6 @@ private ParsedContent parseCloudFlareResultFormat(JsonObject responseJson) {
private static class ParsedContent {
String text;
String thought;
List<NativeToolCall> toolCalls;
}
}
Loading
Loading