mirror of
https://github.com/zhukunpenglinyutong/jetbrains-cc-gui.git
synced 2026-08-29 00:41:38 +08:00
feat: 升级org.jetbrains.intellij.platform
This commit is contained in:
@@ -64,3 +64,5 @@ webview/src/version/version.ts
|
||||
|
||||
# 自定义沙箱目录(如果启用)
|
||||
.sandbox/
|
||||
logs/*.log
|
||||
.intellijPlatform/
|
||||
|
||||
+196
-69
@@ -1,132 +1,212 @@
|
||||
import org.jetbrains.intellij.IntelliJPluginExtension
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.jetbrains.intellij' version '1.17.4'
|
||||
id 'org.jetbrains.intellij.platform' version '2.10.5'
|
||||
}
|
||||
|
||||
group 'com.github.idea-claude-code-gui'
|
||||
version '0.1.0-beta3'
|
||||
group = 'com.github.idea-claude-code-gui'
|
||||
version = '0.1.0-beta3'
|
||||
|
||||
sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
intellijPlatform {
|
||||
defaultRepositories()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
|
||||
intellijPlatform {
|
||||
intellijIdeaCommunity('2024.3.1')
|
||||
instrumentationTools()
|
||||
}
|
||||
}
|
||||
|
||||
// 配置 IntelliJ Platform 插件
|
||||
intellij {
|
||||
version = '2024.3' // 使用 2024.3 稳定版本
|
||||
type = 'IC' // IC = IntelliJ IDEA Community Edition
|
||||
|
||||
// 下载源码和 JavaDoc
|
||||
downloadSources = true
|
||||
|
||||
// 插件依赖(如果需要)
|
||||
plugins = []
|
||||
|
||||
// 禁用字节码插桩,避免生成 instrumented JAR
|
||||
instrumentCode = false
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
// Ensure plugin.xml is patched with the correct since/until build and change notes
|
||||
patchPluginXml {
|
||||
// plugin compatibility range
|
||||
sinceBuild = '233'
|
||||
untilBuild = '263.*' // 兼容到 2026.3 版本,支持更新的 IDE 版本
|
||||
untilBuild = '263.*'
|
||||
|
||||
changeNotes = """
|
||||
<ul>
|
||||
<li>初始版本发布</li>
|
||||
<li>实现右侧工具栏窗口</li>
|
||||
<li>最小支持IDEA 2023.3版本</li>
|
||||
<li>兼容至 IDEA 2026.3 版本(build 263.*)</li>
|
||||
</ul>
|
||||
"""
|
||||
// Release notes shown in plugin repositories
|
||||
// Dynamically load the latest changelog entry from CHANGELOG.md and convert to simple HTML.
|
||||
def changelogFile = file('CHANGELOG.md')
|
||||
def computedChangeNotes = ''
|
||||
if (changelogFile.exists()) {
|
||||
def lines = changelogFile.readLines('UTF-8')
|
||||
// Find first section header (lines starting with '#####')
|
||||
def start = -1
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
if (lines[i].trim().startsWith('#####')) {
|
||||
start = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (start >= 0) {
|
||||
def sb = new StringBuilder()
|
||||
boolean inList = false
|
||||
int i = start + 1
|
||||
while (i < lines.size() && !lines[i].trim().startsWith('#####')) {
|
||||
def line = lines[i]
|
||||
def t = line.trim()
|
||||
|
||||
if (t.startsWith('- ')) {
|
||||
if (!inList) { sb.append('<ul>'); inList = true }
|
||||
def item = t.substring(2)
|
||||
// escape HTML special chars for safety
|
||||
item = item.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
sb.append('<li>' + item + '</li>')
|
||||
} else if (t.startsWith('![') || t.startsWith('<img')) {
|
||||
if (inList) { sb.append('</ul>'); inList = false }
|
||||
// convert markdown image syntax to img tag if necessary
|
||||
if (t.startsWith('![')) {
|
||||
def pattern = Pattern.compile(/!\[(.*?)\]\((.*?)\)/)
|
||||
def matcher = pattern.matcher(t)
|
||||
if (matcher.find()) {
|
||||
def alt = matcher.group(1)
|
||||
def src = matcher.group(2)
|
||||
sb.append("<img alt=\"${alt}\" src=\"${src}\" width=\"500\" />")
|
||||
} else {
|
||||
sb.append(t)
|
||||
}
|
||||
} else {
|
||||
sb.append(t)
|
||||
}
|
||||
} else if (t.length() == 0) {
|
||||
if (inList) { sb.append('</ul>'); inList = false }
|
||||
sb.append('<br/>')
|
||||
} else {
|
||||
if (inList) { sb.append('</ul>'); inList = false }
|
||||
// paragraph
|
||||
def para = t.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
sb.append('<p>' + para + '</p>')
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
if (inList) { sb.append('</ul>') }
|
||||
computedChangeNotes = sb.toString()
|
||||
}
|
||||
}
|
||||
|
||||
changeNotes = computedChangeNotes ?: '<p>No changelog found.</p>'
|
||||
}
|
||||
|
||||
runIde {
|
||||
// 启用 JCEF 支持
|
||||
jvmArgs '-Djcef.sandbox.enable=false'
|
||||
}
|
||||
|
||||
buildSearchableOptions {
|
||||
// Disable product-specific searchable options/instrument tasks if present
|
||||
// Use matching to cover task name variants like buildSearchableOptionsIC / buildSearchableOptionsIU etc.
|
||||
tasks.matching { it.name.startsWith('buildSearchableOptions') }.configureEach {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
// 禁用 instrumented JAR 生成(这是导致 "invalid plugin descriptor" 错误的根源)
|
||||
// instrumented JAR 是用于代码覆盖率测试的,不应该被打包到最终的插件分发包中
|
||||
tasks.named("instrumentCode") {
|
||||
tasks.named('buildSearchableOptions') {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
tasks.named("instrumentTestCode") {
|
||||
tasks.named('instrumentCode') {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
tasks.named('jarSearchableOptions') {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
tasks.named('runIde') {
|
||||
jvmArgumentProviders.add({
|
||||
[
|
||||
'-Djcef.sandbox.enable=false',
|
||||
// 禁用插件自动卸载(避免检测到文件变更时自动卸载)
|
||||
'-Didea.auto.reload.plugins=false',
|
||||
// 禁用插件动态卸载
|
||||
'-Didea.dynamic.plugins.allowed=false',
|
||||
// 启用内部模式
|
||||
'-Didea.is.internal=true',
|
||||
// 禁用文件系统事件监控(可选)
|
||||
// '-Didea.filewatcher.disabled=true',
|
||||
// 增加插件加载超时
|
||||
'-Didea.plugins.load.timeout=60000'
|
||||
]
|
||||
} as CommandLineArgumentProvider)
|
||||
|
||||
// 设置系统属性
|
||||
systemProperty 'idea.auto.reload.plugins', 'false'
|
||||
systemProperty 'idea.dynamic.plugins.allowed', 'false'
|
||||
systemProperty 'idea.is.internal', 'true'
|
||||
systemProperty 'idea.plugin.in.sandbox.mode', 'true'
|
||||
|
||||
// 复制自定义配置到sandbox
|
||||
doFirst {
|
||||
def sandboxConfig = file('sandbox-idea.properties')
|
||||
if (sandboxConfig.exists()) {
|
||||
def sandboxDir = layout.buildDirectory.dir("idea-sandbox").get().asFile
|
||||
def configDir = new File(sandboxDir, 'config')
|
||||
configDir.mkdirs()
|
||||
|
||||
copy {
|
||||
from sandboxConfig
|
||||
into configDir
|
||||
rename { 'idea.properties' }
|
||||
}
|
||||
println "已复制sandbox配置到: ${configDir}/idea.properties"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 统一的 AI Bridge(合并 Claude 和 Codex)
|
||||
def aiBridgeDir = file("ai-bridge")
|
||||
def aiBridgePackDir = file("$buildDir/ai-bridge-pack")
|
||||
def aiBridgeArchive = new File(aiBridgePackDir, "ai-bridge.zip")
|
||||
def aiBridgePackDir = layout.buildDirectory.dir('ai-bridge-pack').get().asFile
|
||||
def aiBridgeArchive = new File(aiBridgePackDir, 'ai-bridge.zip')
|
||||
|
||||
// 自动构建 webview
|
||||
def webviewDir = file('webview')
|
||||
tasks.register('buildWebview', Exec) {
|
||||
workingDir webviewDir
|
||||
|
||||
// 根据操作系统选择命令
|
||||
if (System.getProperty('os.name').toLowerCase().contains('windows')) {
|
||||
commandLine 'cmd', '/c', 'npm', 'run', 'build'
|
||||
} else {
|
||||
commandLine 'npm', 'run', 'build'
|
||||
}
|
||||
|
||||
// 只在 webview 目录存在时执行(使用变量避免配置缓存问题)
|
||||
onlyIf { webviewDir.exists() }
|
||||
|
||||
// 输出构建信息
|
||||
doFirst {
|
||||
println "Building webview..."
|
||||
}
|
||||
doLast {
|
||||
println "Webview build completed."
|
||||
}
|
||||
doFirst { println "Building webview..." }
|
||||
doLast { println "Webview build completed." }
|
||||
}
|
||||
|
||||
tasks.register('packageAiBridge', Zip) {
|
||||
onlyIf { aiBridgeDir.exists() }
|
||||
archiveFileName = "ai-bridge.zip"
|
||||
archiveFileName = 'ai-bridge.zip'
|
||||
destinationDirectory = aiBridgePackDir
|
||||
from(aiBridgeDir)
|
||||
exclude("node_modules/.pnpm/**")
|
||||
exclude('node_modules/.pnpm/**')
|
||||
includeEmptyDirs = true
|
||||
}
|
||||
|
||||
// 提取插件名称,避免在闭包中访问 extensions
|
||||
def intellijExt = project.extensions.getByType(IntelliJPluginExtension)
|
||||
def pluginName = intellijExt.pluginName.getOrElse(project.name)
|
||||
// 提取插件名称
|
||||
def pluginName = project.name
|
||||
|
||||
tasks.named("prepareSandbox") {
|
||||
dependsOn("buildWebview", "packageAiBridge")
|
||||
def sandboxRootProvider = layout.buildDirectory.dir("idea-sandbox")
|
||||
tasks.matching { it.name.startsWith('prepareSandbox') }.configureEach {
|
||||
dependsOn('buildWebview', 'packageAiBridge')
|
||||
def sandboxRootProvider = layout.buildDirectory.dir('idea-sandbox')
|
||||
|
||||
doLast {
|
||||
if (!aiBridgeDir.exists()) {
|
||||
throw new GradleException("ai-bridge 目录不存在,请确保已拉取并安装依赖。")
|
||||
if (!aiBridgeDir.exists() && !aiBridgeArchive.exists()) {
|
||||
throw new GradleException('ai-bridge 目录不存在,请确保已拉取并安装依赖。')
|
||||
}
|
||||
def sandboxRoot = sandboxRootProvider.get().asFile
|
||||
def pluginDir = new File(sandboxRoot, "plugins/${pluginName}")
|
||||
|
||||
// 部署 ai-bridge
|
||||
def extractedDir = new File(pluginDir, "ai-bridge")
|
||||
def archiveTarget = new File(pluginDir, "ai-bridge.zip")
|
||||
def extractedDir = new File(pluginDir, 'ai-bridge')
|
||||
def archiveTarget = new File(pluginDir, 'ai-bridge.zip')
|
||||
project.delete(extractedDir)
|
||||
project.delete(archiveTarget)
|
||||
project.copy {
|
||||
@@ -136,6 +216,53 @@ tasks.named("prepareSandbox") {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("buildPlugin") {
|
||||
dependsOn("packageAiBridge")
|
||||
// 配置 prepareSandbox 之后的任务,确保 ai-bridge.zip 被包含
|
||||
tasks.matching { it.name == 'preparePluginForDistribution' || it.name == 'composedJar' }.configureEach {
|
||||
dependsOn('packageAiBridge')
|
||||
}
|
||||
|
||||
tasks.named('buildPlugin') {
|
||||
dependsOn('packageAiBridge')
|
||||
|
||||
doLast {
|
||||
// 解压插件 zip,添加 ai-bridge.zip,然后重新打包
|
||||
def distributionFile = archiveFile.get().asFile
|
||||
if (distributionFile.exists() && aiBridgeArchive.exists()) {
|
||||
def tempDir = file("${layout.buildDirectory.get()}/tmp/plugin-repack")
|
||||
delete(tempDir)
|
||||
|
||||
// 解压
|
||||
copy {
|
||||
from zipTree(distributionFile)
|
||||
into tempDir
|
||||
}
|
||||
|
||||
// 找到插件目录并添加 ai-bridge.zip
|
||||
def pluginDir = new File(tempDir, pluginName)
|
||||
if (pluginDir.exists()) {
|
||||
copy {
|
||||
from aiBridgeArchive
|
||||
into pluginDir
|
||||
}
|
||||
|
||||
// 删除 searchableOptions jar(如果存在)
|
||||
fileTree(new File(pluginDir, 'lib')).matching {
|
||||
include '**/*searchableOptions*.jar'
|
||||
}.each { jarFile ->
|
||||
delete(jarFile)
|
||||
println "已删除 searchableOptions jar: ${jarFile.name}"
|
||||
}
|
||||
|
||||
// 重新打包
|
||||
delete(distributionFile)
|
||||
ant.zip(destfile: distributionFile) {
|
||||
fileset(dir: tempDir)
|
||||
}
|
||||
println "已将 ai-bridge.zip 添加到插件包并移除 searchableOptions jar"
|
||||
}
|
||||
|
||||
delete(tempDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
kotlin.code.style=official
|
||||
# org.gradle.unsafe.configuration-cache=true
|
||||
org.jetbrains.intellij.buildFeature.selfUpdateCheck=false
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
idea.auto.reload.plugins=false
|
||||
idea.is.internal=true
|
||||
idea.plugin.in.sandbox.mode=true
|
||||
idea.plugins.load.timeout=60000
|
||||
|
||||
+9
-1
@@ -1 +1,9 @@
|
||||
rootProject.name = 'idea-claude-code-gui'
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'idea-claude-code-gui'
|
||||
|
||||
@@ -757,26 +757,48 @@ public class ClaudeSDKToolWindow implements ToolWindowFactory, DumbAware {
|
||||
|
||||
private void pushUsageUpdateFromMessages(List<ClaudeSession.Message> messages) {
|
||||
try {
|
||||
System.out.println("[Backend] pushUsageUpdateFromMessages called with " + messages.size() + " messages");
|
||||
|
||||
JsonObject lastUsage = null;
|
||||
for (int i = messages.size() - 1; i >= 0; i--) {
|
||||
ClaudeSession.Message msg = messages.get(i);
|
||||
if (msg.type != ClaudeSession.Message.Type.ASSISTANT || msg.raw == null) continue;
|
||||
if (!msg.raw.has("message")) continue;
|
||||
JsonObject message = msg.raw.getAsJsonObject("message");
|
||||
if (message.has("usage")) {
|
||||
lastUsage = message.getAsJsonObject("usage");
|
||||
|
||||
if (msg.type != ClaudeSession.Message.Type.ASSISTANT || msg.raw == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查不同的可能结构
|
||||
if (msg.raw.has("message")) {
|
||||
JsonObject message = msg.raw.getAsJsonObject("message");
|
||||
if (message.has("usage")) {
|
||||
lastUsage = message.getAsJsonObject("usage");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查usage是否在raw的根级别
|
||||
if (msg.raw.has("usage")) {
|
||||
lastUsage = msg.raw.getAsJsonObject("usage");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastUsage == null) {
|
||||
System.out.println("[Backend] WARNING: No usage info found in messages!");
|
||||
}
|
||||
|
||||
int inputTokens = lastUsage != null && lastUsage.has("input_tokens") ? lastUsage.get("input_tokens").getAsInt() : 0;
|
||||
int cacheWriteTokens = lastUsage != null && lastUsage.has("cache_creation_input_tokens") ? lastUsage.get("cache_creation_input_tokens").getAsInt() : 0;
|
||||
int cacheReadTokens = lastUsage != null && lastUsage.has("cache_read_input_tokens") ? lastUsage.get("cache_read_input_tokens").getAsInt() : 0;
|
||||
int outputTokens = lastUsage != null && lastUsage.has("output_tokens") ? lastUsage.get("output_tokens").getAsInt() : 0;
|
||||
|
||||
int usedTokens = inputTokens + cacheWriteTokens + cacheReadTokens;
|
||||
int usedTokens = inputTokens + cacheWriteTokens + cacheReadTokens + outputTokens;
|
||||
int maxTokens = MODEL_CONTEXT_LIMITS.getOrDefault(currentModel, 200_000);
|
||||
int percentage = Math.min(100, maxTokens > 0 ? (int) ((usedTokens * 100.0) / maxTokens) : 0);
|
||||
|
||||
System.out.println("[Backend] Pushing usage update: input=" + inputTokens + ", cacheWrite=" + cacheWriteTokens + ", cacheRead=" + cacheReadTokens + ", output=" + outputTokens + ", total=" + usedTokens + ", max=" + maxTokens + ", percentage=" + percentage + "%");
|
||||
|
||||
|
||||
JsonObject usageUpdate = new JsonObject();
|
||||
usageUpdate.addProperty("percentage", percentage);
|
||||
usageUpdate.addProperty("totalTokens", usedTokens);
|
||||
@@ -786,13 +808,22 @@ public class ClaudeSDKToolWindow implements ToolWindowFactory, DumbAware {
|
||||
|
||||
String usageJson = new Gson().toJson(usageUpdate);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
String js = "if (window.onUsageUpdate) { window.onUsageUpdate('" + JsUtils.escapeJs(usageJson) + "'); }";
|
||||
if (browser != null && !disposed) {
|
||||
// 使用安全的调用方式,检查函数是否存在
|
||||
String js = "(function() {" +
|
||||
" if (typeof window.onUsageUpdate === 'function') {" +
|
||||
" window.onUsageUpdate('" + JsUtils.escapeJs(usageJson) + "');" +
|
||||
" console.log('[Backend->Frontend] Usage update sent successfully');" +
|
||||
" } else {" +
|
||||
" console.warn('[Backend->Frontend] window.onUsageUpdate not found');" +
|
||||
" }" +
|
||||
"})();";
|
||||
browser.getCefBrowser().executeJavaScript(js, browser.getCefBrowser().getURL(), 0);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
System.err.println("[Backend] Failed to push usage update: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -842,8 +873,17 @@ public class ClaudeSDKToolWindow implements ToolWindowFactory, DumbAware {
|
||||
usageUpdate.addProperty("maxTokens", maxTokens);
|
||||
|
||||
String usageJson = new Gson().toJson(usageUpdate);
|
||||
String js = "if (window.onUsageUpdate) { window.onUsageUpdate('" + JsUtils.escapeJs(usageJson) + "'); }";
|
||||
|
||||
if (browser != null && !disposed) {
|
||||
// 使用安全的调用方式
|
||||
String js = "(function() {" +
|
||||
" if (typeof window.onUsageUpdate === 'function') {" +
|
||||
" window.onUsageUpdate('" + JsUtils.escapeJs(usageJson) + "');" +
|
||||
" console.log('[Backend->Frontend] Usage reset for new session');" +
|
||||
" } else {" +
|
||||
" console.warn('[Backend->Frontend] window.onUsageUpdate not found');" +
|
||||
" }" +
|
||||
"})();";
|
||||
browser.getCefBrowser().executeJavaScript(js, browser.getCefBrowser().getURL(), 0);
|
||||
}
|
||||
});
|
||||
@@ -879,14 +919,34 @@ public class ClaudeSDKToolWindow implements ToolWindowFactory, DumbAware {
|
||||
|
||||
private void callJavaScript(String functionName, String... args) {
|
||||
if (disposed || browser == null) {
|
||||
System.err.println("[ClaudeSDKToolWindow] 无法调用 JS 函数 " + functionName + ": disposed=" + disposed + ", browser=" + (browser == null ? "null" : "exists"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String js = JsUtils.buildJsCall(functionName, args);
|
||||
browser.getCefBrowser().executeJavaScript(js, browser.getCefBrowser().getURL(), 0);
|
||||
} catch (Exception e) {
|
||||
System.err.println("[ClaudeSDKToolWindow] 调用 JS 函数失败: " + functionName + ", 错误: " + e.getMessage());
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (disposed || browser == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String js = JsUtils.buildJsCall(functionName, args);
|
||||
|
||||
// 先检查函数是否存在,再调用
|
||||
String checkAndCall =
|
||||
"(function() {" +
|
||||
" if (typeof window." + functionName + " === 'function') {" +
|
||||
" " + js +
|
||||
" console.log('[Backend->Frontend] Successfully called " + functionName + "');" +
|
||||
" } else {" +
|
||||
" console.warn('[Backend->Frontend] Function " + functionName + " not found on window');" +
|
||||
" }" +
|
||||
"})();";
|
||||
|
||||
browser.getCefBrowser().executeJavaScript(checkAndCall, browser.getCefBrowser().getURL(), 0);
|
||||
} catch (Exception e) {
|
||||
System.err.println("[ClaudeSDKToolWindow] 调用 JS 函数失败: " + functionName + ", 错误: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -546,6 +546,44 @@ public class ClaudeSession {
|
||||
loading = false;
|
||||
updateState();
|
||||
System.out.println("[ClaudeSession] Message end received, loading set to false");
|
||||
} else if ("result".equals(type) && content.startsWith("{")) {
|
||||
// 处理结果消息(包含最终的usage信息)
|
||||
try {
|
||||
JsonObject resultJson = gson.fromJson(content, JsonObject.class);
|
||||
System.out.println("[ClaudeSession] Result message received");
|
||||
|
||||
// 如果当前消息的raw中usage为0,则用result中的usage进行更新
|
||||
if (currentAssistantMessage != null && currentAssistantMessage.raw != null) {
|
||||
JsonObject message = currentAssistantMessage.raw.has("message") && currentAssistantMessage.raw.get("message").isJsonObject()
|
||||
? currentAssistantMessage.raw.getAsJsonObject("message")
|
||||
: null;
|
||||
|
||||
// 检查当前消息的usage是否全为0
|
||||
boolean needsUsageUpdate = false;
|
||||
if (message != null && message.has("usage")) {
|
||||
JsonObject usage = message.getAsJsonObject("usage");
|
||||
int inputTokens = usage.has("input_tokens") ? usage.get("input_tokens").getAsInt() : 0;
|
||||
int outputTokens = usage.has("output_tokens") ? usage.get("output_tokens").getAsInt() : 0;
|
||||
if (inputTokens == 0 && outputTokens == 0) {
|
||||
needsUsageUpdate = true;
|
||||
}
|
||||
} else {
|
||||
needsUsageUpdate = true;
|
||||
}
|
||||
|
||||
if (needsUsageUpdate && resultJson.has("usage")) {
|
||||
JsonObject resultUsage = resultJson.getAsJsonObject("usage");
|
||||
if (message != null) {
|
||||
message.add("usage", resultUsage);
|
||||
currentAssistantMessage.raw = currentAssistantMessage.raw;
|
||||
notifyMessageUpdate();
|
||||
System.out.println("[ClaudeSession] Updated assistant message usage from result message");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("[ClaudeSession] Failed to parse result message: " + e.getMessage());
|
||||
}
|
||||
} else if ("slash_commands".equals(type)) {
|
||||
// 处理斜杠命令列表
|
||||
try {
|
||||
|
||||
@@ -158,14 +158,14 @@ public class BridgeDirectoryResolver {
|
||||
|
||||
try {
|
||||
String pluginsRoot = PathManager.getPluginsPath();
|
||||
if (pluginsRoot != null && !pluginsRoot.isEmpty()) {
|
||||
if (!pluginsRoot.isEmpty()) {
|
||||
addCandidate(possibleDirs, Paths.get(pluginsRoot, PLUGIN_DIR_NAME, SDK_DIR_NAME).toFile());
|
||||
addCandidate(possibleDirs, Paths.get(pluginsRoot, PLUGIN_ID, SDK_DIR_NAME).toFile());
|
||||
}
|
||||
|
||||
// 使用系统路径下的 plugins 目录代替已废弃的 getPluginTempPath()
|
||||
String systemPath = PathManager.getSystemPath();
|
||||
if (systemPath != null && !systemPath.isEmpty()) {
|
||||
if (!systemPath.isEmpty()) {
|
||||
Path sandboxPath = Paths.get(systemPath, "plugins");
|
||||
addCandidate(possibleDirs, sandboxPath.resolve(PLUGIN_DIR_NAME).resolve(SDK_DIR_NAME).toFile());
|
||||
addCandidate(possibleDirs, sandboxPath.resolve(PLUGIN_ID).resolve(SDK_DIR_NAME).toFile());
|
||||
@@ -282,7 +282,55 @@ public class BridgeDirectoryResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
// 如果在插件目录或 lib 下找不到,尝试查找常见的 sandbox 顶级 plugins 目录和 system/config 下的 plugins
|
||||
List<File> fallbackCandidates = new ArrayList<>();
|
||||
try {
|
||||
// 向上查找祖先,寻找可能的 idea-sandbox 根目录或包含顶级 plugins 的目录
|
||||
File ancestor = pluginDir;
|
||||
int climbs = 0;
|
||||
while (climbs < 6) {
|
||||
File parent = ancestor.getParentFile();
|
||||
if (parent == null) break;
|
||||
|
||||
File maybeTopPlugins = new File(parent, "plugins");
|
||||
if (maybeTopPlugins.exists() && maybeTopPlugins.isDirectory()) {
|
||||
fallbackCandidates.add(new File(maybeTopPlugins, PLUGIN_DIR_NAME + File.separator + SDK_ARCHIVE_NAME));
|
||||
fallbackCandidates.add(new File(maybeTopPlugins, PLUGIN_ID + File.separator + SDK_ARCHIVE_NAME));
|
||||
}
|
||||
|
||||
// system/config siblings under this parent
|
||||
File maybeSystemPlugins = new File(parent, "system/plugins");
|
||||
File maybeConfigPlugins = new File(parent, "config/plugins");
|
||||
if (maybeSystemPlugins.exists() && maybeSystemPlugins.isDirectory()) {
|
||||
fallbackCandidates.add(new File(maybeSystemPlugins, PLUGIN_DIR_NAME + File.separator + SDK_ARCHIVE_NAME));
|
||||
fallbackCandidates.add(new File(maybeSystemPlugins, PLUGIN_ID + File.separator + SDK_ARCHIVE_NAME));
|
||||
}
|
||||
if (maybeConfigPlugins.exists() && maybeConfigPlugins.isDirectory()) {
|
||||
fallbackCandidates.add(new File(maybeConfigPlugins, PLUGIN_DIR_NAME + File.separator + SDK_ARCHIVE_NAME));
|
||||
fallbackCandidates.add(new File(maybeConfigPlugins, PLUGIN_ID + File.separator + SDK_ARCHIVE_NAME));
|
||||
}
|
||||
|
||||
ancestor = parent;
|
||||
climbs++;
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
// ignore fallback discovery errors
|
||||
}
|
||||
|
||||
// 打印并尝试这些候选路径
|
||||
for (File f : fallbackCandidates) {
|
||||
System.out.println("[BridgeResolver] 尝试候选路径: " + f.getAbsolutePath() + " (存在: " + f.exists() + ")");
|
||||
if (f.exists()) {
|
||||
archiveFile = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!archiveFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File extractedDir = new File(pluginDir, SDK_DIR_NAME);
|
||||
@@ -372,26 +420,27 @@ public class BridgeDirectoryResolver {
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动设置 claude-bridge 目录路径
|
||||
* 手动设置 claude-bridge 目录路径.
|
||||
*/
|
||||
public void setSdkDir(String path) {
|
||||
this.cachedSdkDir = new File(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前使用的 claude-bridge 目录
|
||||
* 获取当前使用的 claude-bridge 目录.
|
||||
*/
|
||||
public File getSdkDir() {
|
||||
if (cachedSdkDir == null) {
|
||||
return findSdkDir();
|
||||
if (this.cachedSdkDir == null) {
|
||||
return this.findSdkDir();
|
||||
}
|
||||
return cachedSdkDir;
|
||||
return this.cachedSdkDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* 清除缓存.
|
||||
*/
|
||||
public void clearCache() {
|
||||
this.cachedSdkDir = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ const buildGradlePath = path.join(projectRoot, 'build.gradle');
|
||||
const buildGradleContent = fs.readFileSync(buildGradlePath, 'utf8');
|
||||
|
||||
// 提取版本号
|
||||
// 查找类似 version '0.1.0-beta3' 这样的行
|
||||
let versionMatch = buildGradleContent.match(/^version\s+'(.+)'$/m);
|
||||
// 查找类似 version = '0.1.0-beta3' 这样的行
|
||||
let versionMatch = buildGradleContent.match(/^version\s*=\s*'(.+)'$/m);
|
||||
if (!versionMatch) {
|
||||
// 如果上面的正则失败,尝试另一种方式
|
||||
const lines = buildGradleContent.split('\n');
|
||||
const versionLine = lines.find(line => line.trim().startsWith('version '));
|
||||
const versionLine = lines.find(line => line.trim().startsWith('version ='));
|
||||
if (versionLine) {
|
||||
const match = versionLine.match(/version\s+'(.+)'/);
|
||||
const match = versionLine.match(/version\s*=\s*'(.+)'/);
|
||||
if (match) {
|
||||
versionMatch = match;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user