├── .java-version ├── toolwindow └── .gitkeep ├── claude-agent-sdk ├── test_permission.txt ├── cli-version.properties ├── src │ ├── main │ │ ├── resources │ │ │ ├── cli-version.properties │ │ │ └── patches │ │ │ │ └── README.md │ │ └── kotlin │ │ │ └── com │ │ │ └── asakii │ │ │ ├── claude │ │ │ └── agent │ │ │ │ └── sdk │ │ │ │ ├── ToolParser.kt │ │ │ │ ├── plugin │ │ │ │ ├── types │ │ │ │ │ └── SessionTypes.kt │ │ │ │ └── services │ │ │ │ │ └── SessionStateSyncImpl.kt │ │ │ │ ├── transport │ │ │ │ └── Transport.kt │ │ │ │ ├── callback │ │ │ │ └── ToolCallback.kt │ │ │ │ ├── builders │ │ │ │ └── McpServerBuilder.kt │ │ │ │ └── examples │ │ │ │ └── JointTestClient.kt │ │ │ └── core │ │ │ └── interfaces │ │ │ └── ProjectService.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── asakii │ │ └── claude │ │ └── agent │ │ └── sdk │ │ ├── InvalidModelSwitchTest.kt │ │ ├── CanUseToolCallbackTest.kt │ │ └── PreToolUseHookTest.kt └── cli-patches │ ├── package.json │ └── patches │ └── index.js ├── .mcp.json ├── frontend ├── .npmrc ├── .env.development ├── buf.yaml ├── buf.gen.yaml ├── src │ ├── locales │ │ └── index.ts │ ├── types │ │ ├── denque.d.ts │ │ ├── toast.ts │ │ ├── bridge.ts │ │ └── sessionGroup.ts │ ├── demo-tools.ts │ ├── services │ │ ├── rsocket │ │ │ ├── index.ts │ │ │ └── errorCodes.ts │ │ ├── index.ts │ │ └── localeService.ts │ ├── components │ │ ├── tools │ │ │ ├── GenericToolWrapperMixin.ts │ │ │ ├── CompactToolCallDisplay.vue │ │ │ ├── DiffViewer.vue │ │ │ └── EnterPlanModeToolDisplay.vue │ │ ├── input │ │ │ └── AtSymbolFilePopup.vue │ │ └── chat │ │ │ ├── CompactingCard.vue │ │ │ ├── LocalCommandOutput.vue │ │ │ ├── ErrorResultDisplay.vue │ │ │ └── ContextUsageIndicator.vue │ ├── vite-env.d.ts │ ├── utils │ │ ├── ToolViewModelBuilder.ts │ │ ├── contentBlockUtils.ts │ │ ├── operationQueue.ts │ │ ├── serverUrl.ts │ │ └── scrollBoost.ts │ ├── assets │ │ └── icons │ │ │ └── terminal.svg │ ├── composables │ │ ├── useI18n.ts │ │ ├── useEnvironment.ts │ │ ├── index.ts │ │ ├── useContextMenu.ts │ │ └── useInputResize.ts │ ├── demo │ │ └── toolDemo.ts │ ├── constants │ │ └── messageWindow.ts │ ├── env.d.ts │ ├── config │ │ └── modelConfig.ts │ └── i18n.ts ├── tsconfig.node.json ├── vitest.config.ts ├── public │ └── favicon.svg ├── tsconfig.json ├── .eslintrc.cjs ├── eslint.config.js └── package.json ├── docs └── screenshots │ ├── idea.png │ ├── mcp-config.png │ ├── model-selector.png │ ├── tool-calls-demo.png │ ├── keyboard-shortcuts.png │ ├── permission-request.png │ ├── task-progress-view.png │ ├── jetbrains-marketplace.png │ ├── user-question-dialog.png │ └── at-mention-file-search.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── release └── sonatype │ └── 0.1.0 │ ├── claude-agent-sdk-java-upload │ └── com │ │ └── asakii │ │ └── claude-agent-sdk-java │ │ └── 0.1.0 │ │ ├── claude-agent-sdk-java-0.1.0.jar.md5 │ │ ├── claude-agent-sdk-java-0.1.0.pom.md5 │ │ ├── claude-agent-sdk-java-0.1.0-javadoc.jar.md5 │ │ ├── claude-agent-sdk-java-0.1.0-sources.jar.md5 │ │ ├── claude-agent-sdk-java-0.1.0.jar.sha1 │ │ ├── claude-agent-sdk-java-0.1.0.pom.sha1 │ │ ├── claude-agent-sdk-java-0.1.0-javadoc.jar.sha1 │ │ ├── claude-agent-sdk-java-0.1.0-sources.jar.sha1 │ │ ├── claude-agent-sdk-java-0.1.0.jar │ │ ├── claude-agent-sdk-java-0.1.0-javadoc.jar │ │ ├── claude-agent-sdk-java-0.1.0-sources.jar │ │ ├── claude-agent-sdk-java-0.1.0.jar.asc │ │ ├── claude-agent-sdk-java-0.1.0.pom.asc │ │ ├── claude-agent-sdk-java-0.1.0-javadoc.jar.asc │ │ └── claude-agent-sdk-java-0.1.0-sources.jar.asc │ ├── codex-agent-sdk-java-upload │ └── com │ │ └── asakii │ │ └── codex-agent-sdk-java │ │ └── 0.1.0 │ │ ├── codex-agent-sdk-java-0.1.0.jar.md5 │ │ ├── codex-agent-sdk-java-0.1.0.pom.md5 │ │ ├── codex-agent-sdk-java-0.1.0-javadoc.jar.md5 │ │ ├── codex-agent-sdk-java-0.1.0-sources.jar.md5 │ │ ├── codex-agent-sdk-java-0.1.0.jar.sha1 │ │ ├── codex-agent-sdk-java-0.1.0.pom.sha1 │ │ ├── codex-agent-sdk-java-0.1.0-javadoc.jar.sha1 │ │ ├── codex-agent-sdk-java-0.1.0-sources.jar.sha1 │ │ ├── codex-agent-sdk-java-0.1.0.jar │ │ ├── codex-agent-sdk-java-0.1.0-javadoc.jar │ │ ├── codex-agent-sdk-java-0.1.0-sources.jar │ │ ├── codex-agent-sdk-java-0.1.0.jar.asc │ │ ├── codex-agent-sdk-java-0.1.0.pom.asc │ │ ├── codex-agent-sdk-java-0.1.0-javadoc.jar.asc │ │ └── codex-agent-sdk-java-0.1.0-sources.jar.asc │ ├── claude-agent-sdk-kotlin-upload │ └── com │ │ └── asakii │ │ └── claude-agent-sdk-kotlin │ │ └── 0.1.0 │ │ ├── claude-agent-sdk-kotlin-0.1.0.jar.md5 │ │ ├── claude-agent-sdk-kotlin-0.1.0.pom.md5 │ │ ├── claude-agent-sdk-kotlin-0.1.0-javadoc.jar.md5 │ │ ├── claude-agent-sdk-kotlin-0.1.0-sources.jar.md5 │ │ ├── claude-agent-sdk-kotlin-0.1.0.jar.sha1 │ │ ├── claude-agent-sdk-kotlin-0.1.0.pom.sha1 │ │ ├── claude-agent-sdk-kotlin-0.1.0-javadoc.jar.sha1 │ │ ├── claude-agent-sdk-kotlin-0.1.0-sources.jar.sha1 │ │ ├── claude-agent-sdk-kotlin-0.1.0.jar │ │ ├── claude-agent-sdk-kotlin-0.1.0-javadoc.jar │ │ ├── claude-agent-sdk-kotlin-0.1.0-sources.jar │ │ ├── claude-agent-sdk-kotlin-0.1.0.jar.asc │ │ ├── claude-agent-sdk-kotlin-0.1.0.pom.asc │ │ ├── claude-agent-sdk-kotlin-0.1.0-javadoc.jar.asc │ │ └── claude-agent-sdk-kotlin-0.1.0-sources.jar.asc │ ├── claude-agent-sdk-java-0.1.0-component.zip │ ├── codex-agent-sdk-java-0.1.0-component.zip │ ├── claude-agent-sdk-kotlin-0.1.0-component.zip │ ├── codex-agent-sdk-java │ ├── codex-agent-sdk-java-0.1.0.jar │ ├── codex-agent-sdk-java-0.1.0-javadoc.jar │ └── codex-agent-sdk-java-0.1.0-sources.jar │ ├── claude-agent-sdk-java │ ├── claude-agent-sdk-java-0.1.0.jar │ ├── claude-agent-sdk-java-0.1.0-javadoc.jar │ └── claude-agent-sdk-java-0.1.0-sources.jar │ ├── claude-agent-sdk-kotlin │ ├── claude-agent-sdk-kotlin-0.1.0.jar │ ├── claude-agent-sdk-kotlin-0.1.0-javadoc.jar │ └── claude-agent-sdk-kotlin-0.1.0-sources.jar │ └── codex-agent-sdk-kotlin │ ├── codex-agent-sdk-kotlin-0.1.0.jar │ ├── codex-agent-sdk-kotlin-0.1.0-javadoc.jar │ └── codex-agent-sdk-kotlin-0.1.0-sources.jar ├── .idea └── .gitignore ├── ai-agent-server ├── src │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── asakii │ │ │ ├── bridge │ │ │ ├── EventBridge.kt │ │ │ ├── BridgeProtocol.kt │ │ │ └── ResponseHelper.kt │ │ │ └── server │ │ │ ├── rsocket │ │ │ ├── LoadHistoryRequest.kt │ │ │ └── RSocketErrorCodes.kt │ │ │ ├── mcp │ │ │ ├── GitMcpServerProvider.kt │ │ │ ├── TerminalMcpServerProvider.kt │ │ │ └── JetBrainsMcpServerProvider.kt │ │ │ ├── settings │ │ │ └── ClaudeSettingsPaths.kt │ │ │ ├── rpc │ │ │ └── ClientCaller.kt │ │ │ └── logging │ │ │ └── LazyLogMessage.kt │ │ └── resources │ │ ├── logback.xml │ │ └── logging.properties └── demo-tools │ └── example.ts ├── demo ├── tool-demo.txt ├── ToolsDemo.kt ├── ToolsDemoTest.kt ├── TodoDemo.kt └── TodoDemoTest.kt ├── .claude └── commands │ └── runIde.md ├── codex-agent-sdk ├── src │ └── main │ │ └── kotlin │ │ └── com │ │ └── asakii │ │ └── codex │ │ └── agent │ │ └── sdk │ │ ├── UserInput.kt │ │ ├── CodexOptions.kt │ │ ├── OutputSchemaFile.kt │ │ └── ThreadOptions.kt └── README.md ├── demo-output.txt ├── jetbrains-plugin └── src │ └── main │ ├── kotlin-compat-242 │ └── com │ │ └── asakii │ │ └── plugin │ │ └── compat │ │ ├── LocalizationCompat.kt │ │ ├── VcsCompat.kt │ │ └── BrowseButtonCompat.kt │ ├── kotlin │ └── com │ │ └── asakii │ │ ├── settings │ │ └── ClaudeCodePlusBundle.kt │ │ └── plugin │ │ ├── services │ │ ├── ProjectSessionStateService.kt │ │ ├── ClaudeCodePlusBackgroundService.kt │ │ ├── GitBranchService.kt │ │ ├── GitBranchServiceImpl.kt │ │ └── NoopLanguageAnalysisService.kt │ │ ├── listeners │ │ └── ToolWindowStateChangedTopic.kt │ │ ├── types │ │ ├── SessionTypes.kt │ │ ├── ToolDetails.kt │ │ ├── UiModels.kt │ │ └── ToolConstants.kt │ │ ├── startup │ │ └── PluginStartup.kt │ │ ├── adapters │ │ ├── ProjectServiceAdapter.kt │ │ └── IdeIntegration.kt │ │ ├── mcp │ │ ├── git │ │ │ ├── GetCommitMessageTool.kt │ │ │ └── SetCommitMessageTool.kt │ │ └── tools │ │ │ └── terminal │ │ │ ├── TerminalTypesTool.kt │ │ │ ├── TerminalInterruptTool.kt │ │ │ ├── TerminalRenameTool.kt │ │ │ ├── TerminalListTool.kt │ │ │ └── TerminalKillTool.kt │ │ ├── handlers │ │ ├── WriteToolHandler.kt │ │ ├── ToolClickHandler.kt │ │ └── ReadToolHandler.kt │ │ ├── theme │ │ └── IdeaThemeAdapter.kt │ │ ├── ui │ │ └── title │ │ │ └── NewSessionAction.kt │ │ └── config │ │ └── PluginConfig.kt │ ├── resources │ ├── META-INF │ │ ├── plugin-withGit.xml │ │ ├── plugin-withJava.xml │ │ └── pluginIcon.svg │ ├── logging.properties │ ├── icons │ │ ├── claude-ai.svg │ │ └── claude-code-simple.svg │ └── messages │ │ ├── ClaudeCodePlusBundle.properties │ │ ├── ClaudeCodePlusBundle_zh_CN.properties │ │ ├── ClaudeCodePlusBundle_ko.properties │ │ └── ClaudeCodePlusBundle_ja.properties │ ├── kotlin-compat-253 │ └── com │ │ └── asakii │ │ └── plugin │ │ └── compat │ │ ├── LocalizationCompat.kt │ │ ├── VcsCompat.kt │ │ └── BrowseButtonCompat.kt │ ├── kotlin-compat-diff-242 │ └── com │ │ └── asakii │ │ └── plugin │ │ └── compat │ │ └── DiffEditorCompat.kt │ ├── kotlin-compat-diff-243 │ └── com │ │ └── asakii │ │ └── plugin │ │ └── compat │ │ └── DiffEditorCompat.kt │ └── kotlin-compat-243 │ └── com │ └── asakii │ └── plugin │ └── compat │ └── BrowseButtonCompat.kt ├── ai-agent-sdk ├── src │ └── main │ │ └── kotlin │ │ └── com │ │ └── asakii │ │ └── ai │ │ └── agent │ │ └── sdk │ │ ├── AiAgentProvider.kt │ │ ├── capabilities │ │ ├── PermissionMode.kt │ │ ├── AgentCapabilities.kt │ │ └── DefaultCapabilities.kt │ │ ├── client │ │ └── UnifiedAgentClientFactory.kt │ │ └── AiAgentStreamBridge.kt └── build.gradle.kts ├── ai-agent-rpc-api └── build.gradle.kts ├── standalone-test └── build.gradle.kts ├── .gitmodules ├── LICENSE ├── edit_proto.py ├── settings.gradle.kts ├── gradle.properties └── ai-agent-proto └── build.gradle.kts /.java-version: -------------------------------------------------------------------------------- 1 | 21 2 | -------------------------------------------------------------------------------- /toolwindow/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /claude-agent-sdk/test_permission.txt: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /.mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers":{ 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /frontend/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com 2 | -------------------------------------------------------------------------------- /frontend/.env.development: -------------------------------------------------------------------------------- 1 | # 开发模式配置 2 | # 后端服务器端口(需要手动更新为实际端口) 3 | VITE_BACKEND_PORT=8765 4 | 5 | -------------------------------------------------------------------------------- /docs/screenshots/idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/idea.png -------------------------------------------------------------------------------- /docs/screenshots/mcp-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/mcp-config.png -------------------------------------------------------------------------------- /docs/screenshots/model-selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/model-selector.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /docs/screenshots/tool-calls-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/tool-calls-demo.png -------------------------------------------------------------------------------- /docs/screenshots/keyboard-shortcuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/keyboard-shortcuts.png -------------------------------------------------------------------------------- /docs/screenshots/permission-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/permission-request.png -------------------------------------------------------------------------------- /docs/screenshots/task-progress-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/task-progress-view.png -------------------------------------------------------------------------------- /docs/screenshots/jetbrains-marketplace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/jetbrains-marketplace.png -------------------------------------------------------------------------------- /docs/screenshots/user-question-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/user-question-dialog.png -------------------------------------------------------------------------------- /docs/screenshots/at-mention-file-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/docs/screenshots/at-mention-file-search.png -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0.jar.md5: -------------------------------------------------------------------------------- 1 | 923148492332cada81d9f9791647af75 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0.pom.md5: -------------------------------------------------------------------------------- 1 | 5afd2099f61a6d35317f682fcb0e6ce4 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0.jar.md5: -------------------------------------------------------------------------------- 1 | 25a6a25adbfa5b135e921001ae31fdf9 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0.pom.md5: -------------------------------------------------------------------------------- 1 | 8c0cdad2989a1ff90da4d50e8d933ffb -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | 414554466fff605b84b19f242f0a4bae -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-sources.jar.md5: -------------------------------------------------------------------------------- 1 | 649e787e396c4f7da1f7c05783f0250c -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | 7073e89c11d04ff45c7b43f14fd24ba7 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-sources.jar.md5: -------------------------------------------------------------------------------- 1 | efa48ad6d5892474ff3793dccc9acdff -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0.jar.sha1: -------------------------------------------------------------------------------- 1 | 46cbd41147028c2b6d6c8c46509e9af9300101ee -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0.pom.sha1: -------------------------------------------------------------------------------- 1 | 9b43675aa1d16063310147cfe700192be90d07db -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0.jar.md5: -------------------------------------------------------------------------------- 1 | 923148492332cada81d9f9791647af75 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0.pom.md5: -------------------------------------------------------------------------------- 1 | 70bcb853722a822eb6161273bd49cb57 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0.jar.sha1: -------------------------------------------------------------------------------- 1 | c34e4b880710a9d400fa7523246ecba65b893e9b -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0.pom.sha1: -------------------------------------------------------------------------------- 1 | b9c6dc9809a46ba3428151c47bdeefffc01964eb -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-javadoc.jar.md5: -------------------------------------------------------------------------------- 1 | 7073e89c11d04ff45c7b43f14fd24ba7 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-sources.jar.md5: -------------------------------------------------------------------------------- 1 | efa48ad6d5892474ff3793dccc9acdff -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0.jar.sha1: -------------------------------------------------------------------------------- 1 | 46cbd41147028c2b6d6c8c46509e9af9300101ee -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0.pom.sha1: -------------------------------------------------------------------------------- 1 | 2742c7faf4fc8cd6e813ee172931d2e7c6c8d1ba -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | 6c2b260be129a8844557e62e416a825e7507393d -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | e85072bbd6ad68b14433dc2f3f4e8a8923ea9e1d -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | e03522d224612e4482b65d193ec16254779eb0d1 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | a9656dfa24bccfc9d554ab1d7bd4abc72e2a26a8 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-javadoc.jar.sha1: -------------------------------------------------------------------------------- 1 | e03522d224612e4482b65d193ec16254779eb0d1 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-sources.jar.sha1: -------------------------------------------------------------------------------- 1 | a9656dfa24bccfc9d554ab1d7bd4abc72e2a26a8 -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-0.1.0-component.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-java-0.1.0-component.zip -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-0.1.0-component.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-java-0.1.0-component.zip -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-0.1.0-component.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-kotlin-0.1.0-component.zip -------------------------------------------------------------------------------- /frontend/buf.yaml: -------------------------------------------------------------------------------- 1 | # buf.yaml - Buf 模块配置 2 | version: v2 3 | modules: 4 | - path: ../ai-agent-proto/src/main/proto 5 | lint: 6 | use: 7 | - STANDARD 8 | breaking: 9 | use: 10 | - FILE 11 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java/codex-agent-sdk-java-0.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-java/codex-agent-sdk-java-0.1.0.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java/claude-agent-sdk-java-0.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-java/claude-agent-sdk-java-0.1.0.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin/claude-agent-sdk-kotlin-0.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-kotlin/claude-agent-sdk-kotlin-0.1.0.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-kotlin/codex-agent-sdk-kotlin-0.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-kotlin/codex-agent-sdk-kotlin-0.1.0.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java/codex-agent-sdk-java-0.1.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-java/codex-agent-sdk-java-0.1.0-javadoc.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java/codex-agent-sdk-java-0.1.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-java/codex-agent-sdk-java-0.1.0-sources.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java/claude-agent-sdk-java-0.1.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-java/claude-agent-sdk-java-0.1.0-javadoc.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java/claude-agent-sdk-java-0.1.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-java/claude-agent-sdk-java-0.1.0-sources.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-kotlin/codex-agent-sdk-kotlin-0.1.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-kotlin/codex-agent-sdk-kotlin-0.1.0-javadoc.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-kotlin/codex-agent-sdk-kotlin-0.1.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-kotlin/codex-agent-sdk-kotlin-0.1.0-sources.jar -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | 10 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin/claude-agent-sdk-kotlin-0.1.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-kotlin/claude-agent-sdk-kotlin-0.1.0-javadoc.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin/claude-agent-sdk-kotlin-0.1.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-kotlin/claude-agent-sdk-kotlin-0.1.0-sources.jar -------------------------------------------------------------------------------- /frontend/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | # buf.gen.yaml - Protobuf 代码生成配置 2 | version: v2 3 | plugins: 4 | # 生成 TypeScript 代码(使用 @bufbuild/protobuf) 5 | - local: protoc-gen-es 6 | out: src/proto 7 | opt: 8 | - target=ts 9 | - import_extension=none 10 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/bridge/EventBridge.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.bridge 2 | 3 | /** 4 | * 事件桥接接口 5 | * 用于后端向前端推送事件 6 | */ 7 | interface EventBridge { 8 | /** 9 | * 推送事件到前端 10 | */ 11 | fun pushEvent(event: IdeEvent) 12 | } 13 | 14 | -------------------------------------------------------------------------------- /demo/tool-demo.txt: -------------------------------------------------------------------------------- 1 | # 工具演示文件 2 | 3 | 这是使用 Write 工具创建的新文件。 4 | 5 | ## 待编辑内容 6 | 7 | - 项目名称: Claude Code Plus 8 | - 版本号: 2.0.0 9 | - 作者: Asakii 10 | 11 | ## 功能列表 12 | 13 | 1. 功能A - 已完成 ✓ 14 | 2. 功能B - 已完成 ✓ 15 | 3. 功能C - 已完成 ✓ 16 | 17 | --- 18 | 创建时间: 2025-12-15 19 | -------------------------------------------------------------------------------- /frontend/src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import zhCN from './zh-CN' 2 | import enUS from './en-US' 3 | import koKR from './ko-KR' 4 | import jaJP from './ja-JP' 5 | 6 | export default { 7 | 'zh-CN': zhCN, 8 | 'en-US': enUS, 9 | 'ko-KR': koKR, 10 | 'ja-JP': jaJP 11 | } 12 | 13 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0.jar -------------------------------------------------------------------------------- /.claude/commands/runIde.md: -------------------------------------------------------------------------------- 1 | --- 2 | allowed-tools: Bash(git:*), Read 3 | argument-hint: 4 | description: 启动 ide 测试插件功能 5 | --- 6 | 7 | 后台执行执行./gradlew jetbrains-plugin:runIde 命令 8 | 如果后台已经启动,你也执行,以命令会重启 ide,而不是什么都不做 9 | 不要执行其他任何操作,也不要等待命令结束,后续可以查询日志 10 | 11 | **复杂多步骤任务** - 需要3个/r或更多步骤时 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-javadoc.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-sources.jar -------------------------------------------------------------------------------- /codex-agent-sdk/src/main/kotlin/com/asakii/codex/agent/sdk/UserInput.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.codex.agent.sdk 2 | 3 | import java.nio.file.Path 4 | 5 | sealed interface UserInput { 6 | data class Text(val text: String) : UserInput 7 | data class LocalImage(val path: Path) : UserInput 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-javadoc.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-sources.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0.jar -------------------------------------------------------------------------------- /frontend/src/types/denque.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'denque' { 2 | export default class Denque { 3 | constructor(items?: T[]) 4 | push(item: T): number 5 | unshift(item: T): number 6 | pop(): T | undefined 7 | shift(): T | undefined 8 | toArray(): T[] 9 | readonly length: number 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-javadoc.jar -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touwaeriol/claude-code-plus/HEAD/release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-sources.jar -------------------------------------------------------------------------------- /claude-agent-sdk/cli-version.properties: -------------------------------------------------------------------------------- 1 | # Claude CLI version bundled with this SDK 2 | # This version will be downloaded during build and packaged into the JAR 3 | # You can use "stable" to always download the latest stable version 4 | cli.version=2.0.73 5 | # NPM package version for @anthropic-ai/claude-agent-sdk 6 | npm.version=0.1.73 7 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/resources/cli-version.properties: -------------------------------------------------------------------------------- 1 | # Claude CLI version bundled with this SDK 2 | # This version will be downloaded during build and packaged into the JAR 3 | # You can use "stable" to always download the latest stable version 4 | cli.version=2.0.73 5 | # NPM package version for @anthropic-ai/claude-agent-sdk 6 | npm.version=0.1.73 7 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/server/rsocket/LoadHistoryRequest.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.server.rsocket 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class LoadHistoryRequest( 7 | val sessionId: String? = null, 8 | val projectPath: String? = null, 9 | val offset: Int = 0, 10 | val limit: Int = 0 11 | ) 12 | -------------------------------------------------------------------------------- /demo-output.txt: -------------------------------------------------------------------------------- 1 | # Claude Code Plus - Tool Demo 2 | 3 | This file was created by the Write tool. 4 | 5 | ## Timestamp 6 | 2025-12-21 7 | 8 | ## Project Modules 9 | - toolwindow 10 | - jetbrains-plugin 11 | - claude-agent-sdk 12 | - codex-agent-sdk 13 | - ai-agent-sdk 14 | - ai-agent-rpc-api 15 | - ai-agent-proto 16 | - standalone-test 17 | - ai-agent-server 18 | 19 | ## Demo Complete! 20 | -------------------------------------------------------------------------------- /frontend/src/demo-tools.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 工具演示文件 3 | * 4 | * 这个文件用于演示 Read、Write、Edit 工具的使用 5 | */ 6 | 7 | // 示例函数 8 | function greet(name: string): string { 9 | return `Hello, ${name}!` 10 | } 11 | 12 | // 新增:告别函数 13 | function farewell(name: string): string { 14 | return `Goodbye, ${name}! See you next time.` 15 | } 16 | 17 | // 示例常量 18 | const VERSION = '1.0.0' 19 | 20 | // 导出 21 | export { greet, farewell, VERSION } 22 | -------------------------------------------------------------------------------- /frontend/src/services/rsocket/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * RSocket 模块导出 3 | * 4 | * 提供基于 RSocket + Protobuf 的 AI Agent 通信能力 5 | */ 6 | 7 | export { RSocketClient, createRSocketClient, type RSocketClientOptions, type StreamSubscriber } from './RSocketClient' 8 | export { RSocketSession, type ConnectOptions, type AgentStreamEvent, type ContentBlock } from './RSocketSession' 9 | export { ProtoCodec, Provider, PermissionMode, SandboxMode } from './protoCodec' 10 | -------------------------------------------------------------------------------- /demo/ToolsDemo.kt: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | /** 4 | * 工具演示类 5 | * 用于展示 Claude Code Plus 的各种工具功能 6 | */ 7 | class ToolsDemo { 8 | // 临时变量名,待重构 9 | private val configData = "临时数据" 10 | 11 | fun greet(name: String): String { 12 | return "Hello, $name!" 13 | } 14 | 15 | fun calculate(a: Int, b: Int): Int { 16 | return a + b 17 | } 18 | 19 | fun displayConfig() { 20 | println("临时变量: $configData") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/components/tools/GenericToolWrapperMixin.ts: -------------------------------------------------------------------------------- 1 | import type { ToolCall } from '@/types/display' 2 | import { ToolCallStatus } from '@/types/display' 3 | 4 | export interface BaseToolProps { 5 | toolCall: ToolCall 6 | } 7 | 8 | export function buildStatus(toolCall: ToolCall) { 9 | return { 10 | isSuccess: toolCall.status === ToolCallStatus.SUCCESS, 11 | isFailed: toolCall.status === ToolCallStatus.FAILED, 12 | isRunning: toolCall.status === ToolCallStatus.RUNNING 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // SVG 文件类型声明 4 | declare module '*.svg' { 5 | const content: string 6 | export default content 7 | } 8 | 9 | // SVG 原始内容导入(?raw) 10 | declare module '*.svg?raw' { 11 | const content: string 12 | export default content 13 | } 14 | 15 | // SVG 作为组件导入(?component) 16 | declare module '*.svg?component' { 17 | import { DefineComponent } from 'vue' 18 | const component: DefineComponent 19 | export default component 20 | } 21 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin-compat-242/com/asakii/plugin/compat/LocalizationCompat.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.compat 2 | 3 | import java.util.Locale 4 | 5 | /** 6 | * 本地化兼容层 - 适用于 2024.1 ~ 2025.2 7 | * 8 | * 在这些版本中,com.intellij.l10n.LocalizationUtil 不存在 9 | * 使用 Java 标准 Locale 作为替代 10 | */ 11 | object LocalizationCompat { 12 | 13 | /** 14 | * 获取当前语言环境 15 | * @return Locale 对象 16 | */ 17 | fun getLocale(): Locale { 18 | return Locale.getDefault() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/services/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务层统一入口 3 | * 导出所有服务和事件监听 4 | */ 5 | 6 | import { ideService, ideaBridge, aiAgentBridgeService } from './ideaBridge' 7 | 8 | // 导出所有服务 9 | export { 10 | aiAgentBridgeService, 11 | ideService, 12 | ideaBridge 13 | } 14 | 15 | // 导出 RSocketSession 类供直接使用(已迁移到 RSocket + Protobuf) 16 | export { RSocketSession } from './rsocket' 17 | export type { ConnectOptions } from './rsocket' 18 | // 向后兼容的类型别名 19 | export { RSocketSession as AiAgentSession } from './rsocket' 20 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/kotlin/com/asakii/claude/agent/sdk/ToolParser.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk 2 | 3 | import kotlinx.serialization.json.JsonObject 4 | 5 | /** 6 | * 简化的工具解析器 7 | * 用于解析工具调用参数 8 | */ 9 | object ToolParser { 10 | 11 | /** 12 | * 解析工具参数 13 | * 这是一个简化的实现,主要用于编译通过 14 | */ 15 | fun parseToolParameters(toolName: String, input: JsonObject): Any? { 16 | // 简化实现:返回null,表示没有特定的工具实例 17 | // 在实际使用中,这里可以根据工具名称创建对应的工具实例 18 | return null 19 | } 20 | } -------------------------------------------------------------------------------- /ai-agent-sdk/src/main/kotlin/com/asakii/ai/agent/sdk/AiAgentProvider.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.ai.agent.sdk 2 | 3 | /** 4 | * 标识统一 SDK 当前使用的底层 AI Agent 提供方。 5 | */ 6 | enum class AiAgentProvider { 7 | CLAUDE, 8 | CODEX; 9 | 10 | val isClaude: Boolean get() = this == CLAUDE 11 | val isCodex: Boolean get() = this == CODEX 12 | } 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /claude-agent-sdk/cli-patches/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "claude-cli-patches", 3 | "version": "1.0.0", 4 | "description": "AST-based patches for Claude CLI enhancement", 5 | "main": "patch-cli.js", 6 | "scripts": { 7 | "patch": "node patch-cli.js", 8 | "test": "node patch-cli.js --dry-run" 9 | }, 10 | "dependencies": { 11 | "@babel/parser": "^7.23.0", 12 | "@babel/traverse": "^7.23.0", 13 | "@babel/generator": "^7.23.0", 14 | "@babel/types": "^7.23.0" 15 | }, 16 | "engines": { 17 | "node": ">=18.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import vue from '@vitejs/plugin-vue' 3 | import { resolve } from 'path' 4 | 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | test: { 8 | globals: true, 9 | environment: 'happy-dom', 10 | include: ['src/**/*.{test,spec}.{js,ts}'], 11 | coverage: { 12 | reporter: ['text', 'json', 'html'], 13 | include: ['src/utils/**/*.ts'] 14 | } 15 | }, 16 | resolve: { 17 | alias: { 18 | '@': resolve(__dirname, 'src') 19 | } 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/settings/ClaudeCodePlusBundle.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.settings 2 | 3 | import com.intellij.DynamicBundle 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | 7 | @NonNls 8 | private const val BUNDLE = "messages.ClaudeCodePlusBundle" 9 | 10 | object ClaudeCodePlusBundle : DynamicBundle(BUNDLE) { 11 | @JvmStatic 12 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String { 13 | return getMessage(key, *params) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ai-agent-sdk/src/main/kotlin/com/asakii/ai/agent/sdk/capabilities/PermissionMode.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.ai.agent.sdk.capabilities 2 | 3 | /** 4 | * AI Agent 权限模式枚举 5 | * 6 | * 定义 Agent 执行工具时的权限控制级别。 7 | * 不同模式决定了工具调用时是否需要用户确认。 8 | */ 9 | enum class AiPermissionMode { 10 | /** 11 | * 默认模式:危险工具需要用户确认 12 | */ 13 | DEFAULT, 14 | 15 | /** 16 | * 自动接受文件编辑:文件相关操作自动批准 17 | */ 18 | ACCEPT_EDITS, 19 | 20 | /** 21 | * 跳过所有权限检查:所有工具自动执行(谨慎使用) 22 | */ 23 | BYPASS_PERMISSIONS, 24 | 25 | /** 26 | * 计划模式:只分析不执行,用于预览操作 27 | */ 28 | PLAN 29 | } 30 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/META-INF/plugin-withGit.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin-compat-253/com/asakii/plugin/compat/LocalizationCompat.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.compat 2 | 3 | import com.intellij.l10n.LocalizationUtil 4 | import java.util.Locale 5 | 6 | /** 7 | * 本地化兼容层 - 适用于 2025.3+ 8 | * 9 | * 使用 IntelliJ 平台的 LocalizationUtil API 10 | */ 11 | object LocalizationCompat { 12 | 13 | /** 14 | * 获取当前语言环境 15 | * @return Locale 对象 16 | */ 17 | fun getLocale(): Locale { 18 | return try { 19 | LocalizationUtil.getLocale(true) 20 | } catch (e: Exception) { 21 | Locale.getDefault() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/ToolsDemoTest.kt: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * 工具演示测试类 7 | */ 8 | class ToolsDemoTest { 9 | 10 | @Test 11 | fun testOldMethodName() { 12 | val demo = ToolsDemo() 13 | demo.oldMethodName() 14 | } 15 | 16 | @Test 17 | fun testProcessData() { 18 | val demo = ToolsDemo() 19 | val result = demo.processData("测试数据") 20 | assert(result.contains("测试数据")) 21 | } 22 | 23 | @Test 24 | fun testTempVariable() { 25 | val demo = ToolsDemo() 26 | demo.displayConfig() 27 | // 这里使用了 configData 相关的功能 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/server/rsocket/RSocketErrorCodes.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.server.rsocket 2 | 3 | /** 4 | * 自定义 RSocket 错误码 5 | * 6 | * RSocket 协议允许自定义错误码范围: 0x00000301 - 0xFFFFFFFE 7 | * 我们使用 0x00010000 开头的范围作为应用自定义错误码 8 | */ 9 | object RSocketErrorCodes { 10 | /** 11 | * 客户端未连接错误 12 | * 当 Claude CLI 进程意外退出或连接断开时抛出 13 | * 前端收到此错误码应触发自动重连 14 | */ 15 | const val NOT_CONNECTED = 0x00010001 16 | 17 | /** 18 | * 会话已过期 19 | */ 20 | const val SESSION_EXPIRED = 0x00010002 21 | 22 | /** 23 | * 认证失败 24 | */ 25 | const val AUTH_FAILED = 0x00010003 26 | } 27 | -------------------------------------------------------------------------------- /ai-agent-sdk/src/main/kotlin/com/asakii/ai/agent/sdk/client/UnifiedAgentClientFactory.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.ai.agent.sdk.client 2 | 3 | import com.asakii.ai.agent.sdk.AiAgentProvider 4 | 5 | object UnifiedAgentClientFactory { 6 | fun create(provider: AiAgentProvider): UnifiedAgentClient = when (provider) { 7 | AiAgentProvider.CLAUDE -> ClaudeAgentClientImpl() 8 | AiAgentProvider.CODEX -> CodexAgentClientImpl() 9 | } 10 | } 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin-compat-242/com/asakii/plugin/compat/VcsCompat.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.compat 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.vcs.AbstractVcs 5 | import com.intellij.openapi.vcs.ProjectLevelVcsManager 6 | 7 | /** 8 | * VCS 兼容层 - 适用于 2024.2 ~ 2025.2 9 | * 10 | * 在这些版本中,使用标准的 allActiveVcss 属性 11 | */ 12 | object VcsCompat { 13 | 14 | /** 15 | * 获取项目中所有活跃的 VCS 16 | */ 17 | fun getAllActiveVcss(project: Project): Array { 18 | val vcsManager = ProjectLevelVcsManager.getInstance(project) 19 | return vcsManager.allActiveVcss 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /claude-agent-sdk/cli-patches/patches/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 补丁注册表 3 | * 4 | * 按顺序加载和应用补丁 5 | */ 6 | 7 | const runInBackground = require('./001-run-in-background'); 8 | const chromeStatus = require('./002-chrome-status'); 9 | const parentUuid = require('./003-parent-uuid'); 10 | const mcpReconnect = require('./004-mcp-reconnect'); 11 | const mcpTools = require('./005-mcp-tools'); 12 | const mcpDisableEnable = require('./006-mcp-disable-enable'); 13 | 14 | // 按优先级排序导出所有补丁 15 | module.exports = [ 16 | runInBackground, 17 | chromeStatus, 18 | parentUuid, 19 | mcpReconnect, 20 | mcpTools, 21 | mcpDisableEnable, 22 | ].sort((a, b) => (a.priority || 100) - (b.priority || 100)); 23 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/server/mcp/GitMcpServerProvider.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.server.mcp 2 | 3 | import com.asakii.claude.agent.sdk.mcp.McpServer 4 | 5 | /** 6 | * Git MCP 服务器提供者接口 7 | * 8 | * 由于 Git MCP 需要访问 IDEA VCS API, 9 | * 实现类必须在 jetbrains-plugin 模块中创建。 10 | * 此接口用于解耦 ai-agent-server 和 jetbrains-plugin 的依赖。 11 | */ 12 | interface GitMcpServerProvider { 13 | /** 14 | * 获取 Git MCP 服务器实例 15 | * @return McpServer 实例,如果不可用则返回 null 16 | */ 17 | fun getServer(): McpServer? 18 | } 19 | 20 | /** 21 | * 默认实现(不支持 Git 集成时使用) 22 | */ 23 | object DefaultGitMcpServerProvider : GitMcpServerProvider { 24 | override fun getServer(): McpServer? = null 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/utils/ToolViewModelBuilder.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 工具 ViewModel 构建(简化版,直接基于 ToolCall) 3 | */ 4 | import type { ToolCall } from '@/types/display' 5 | 6 | export interface ToolCallViewModel { 7 | toolType?: string 8 | compactSummary?: string 9 | toolDetail?: { 10 | parameters?: Record 11 | toolType?: string 12 | } 13 | toolResult?: any 14 | } 15 | 16 | export function buildToolViewModel(toolCall: ToolCall): ToolCallViewModel { 17 | return { 18 | toolType: toolCall.toolType, 19 | compactSummary: undefined, 20 | toolDetail: { 21 | parameters: toolCall.input, 22 | toolType: toolCall.toolType 23 | }, 24 | toolResult: toolCall.result 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/bridge/BridgeProtocol.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.bridge 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlinx.serialization.json.JsonElement 5 | 6 | /** 7 | * 前后端通信协议定义 8 | */ 9 | 10 | @Serializable 11 | data class FrontendRequest( 12 | val action: String, 13 | val data: JsonElement? = null 14 | ) 15 | 16 | @Serializable 17 | data class FrontendResponse( 18 | val success: Boolean, 19 | val data: Map? = null, 20 | val error: String? = null 21 | ) 22 | 23 | @Serializable 24 | data class IdeEvent( 25 | val type: String, 26 | val data: Map? = null 27 | ) 28 | 29 | // IdeTheme 已移至 com.asakii.rpc.api.IdeTheme 30 | 31 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/resources/logging.properties: -------------------------------------------------------------------------------- 1 | # Java Util Logging 配置文件 2 | # 设置编码为 UTF-8,解决 Windows 控制台乱码问题 3 | 4 | # 根日志级别 5 | .level=INFO 6 | 7 | # 指定使用的处理器(重要:必须显式指定 ConsoleHandler) 8 | handlers=java.util.logging.ConsoleHandler 9 | 10 | # 控制台处理器配置 11 | java.util.logging.ConsoleHandler.level=INFO 12 | java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter 13 | java.util.logging.ConsoleHandler.encoding=UTF-8 14 | 15 | # 简单格式化器配置 16 | java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS [%4$-7s] %5$s%6$s%n 17 | 18 | # 特定包的日志级别(可以设置为更详细的级别用于调试) 19 | com.asakii.level=INFO 20 | com.asakii.claude.level=INFO 21 | com.asakii.server.level=INFO 22 | com.asakii.ai.agent.level=INFO 23 | 24 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/logging.properties: -------------------------------------------------------------------------------- 1 | # Java Util Logging 配置文件 2 | # 设置编码为 UTF-8,解决 Windows 控制台乱码问题 3 | 4 | # 根日志级别 5 | .level=INFO 6 | 7 | # 指定使用的处理器(重要:必须显式指定 ConsoleHandler) 8 | handlers=java.util.logging.ConsoleHandler 9 | 10 | # 控制台处理器配置 11 | java.util.logging.ConsoleHandler.level=INFO 12 | java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter 13 | java.util.logging.ConsoleHandler.encoding=UTF-8 14 | 15 | # 简单格式化器配置 16 | java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS [%4$-7s] %5$s%6$s%n 17 | 18 | # 特定包的日志级别(可以设置为更详细的级别用于调试) 19 | com.asakii.level=INFO 20 | com.asakii.claude.level=INFO 21 | com.asakii.server.level=INFO 22 | com.asakii.ai.agent.level=INFO 23 | 24 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/terminal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/composables/useI18n.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 国际化 composable 3 | * 基于 vue-i18n,保持现有组件 API 兼容 4 | */ 5 | 6 | import { useI18n as vueUseI18n } from 'vue-i18n' 7 | import { computed } from 'vue' 8 | 9 | export function useI18n() { 10 | const { t, locale } = vueUseI18n() 11 | 12 | return { 13 | t, 14 | locale, 15 | /** 16 | * 检查翻译键是否存在 17 | */ 18 | hasKey(key: string): boolean { 19 | const result = t(key) 20 | return result !== key 21 | }, 22 | isChinese: computed(() => locale.value === 'zh-CN'), 23 | isEnglish: computed(() => locale.value === 'en-US'), 24 | isKorean: computed(() => locale.value === 'ko-KR'), 25 | isJapanese: computed(() => locale.value === 'ja-JP') 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ai-agent-rpc-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | 4 | plugins { 5 | kotlin("jvm") 6 | kotlin("plugin.serialization") 7 | } 8 | 9 | dependencies { 10 | // Kotlinx Serialization 11 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${rootProject.extra["serializationVersion"]}") 12 | 13 | // Coroutines for Flow 14 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${rootProject.extra["coroutinesVersion"]}") 15 | 16 | // Claude Agent SDK - AgentDefinition 类型由 SDK 统一提供 17 | api(project(":claude-agent-sdk")) 18 | } 19 | 20 | 21 | kotlin { 22 | jvmToolchain(17) 23 | } 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/src/demo/toolDemo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 工具演示文件 3 | * 此文件用于演示 Claude Code 的各种工具功能 4 | */ 5 | 6 | export interface User { 7 | id: number 8 | name: string 9 | emailAddress: string 10 | } 11 | 12 | export function greetUser(user: User): string { 13 | return `Hello, ${user.name}!` 14 | } 15 | 16 | export function formatEmail(emailAddress: string): string { 17 | return emailAddress.toLowerCase().trim() 18 | } 19 | 20 | export function validateEmail(emailAddress: string): boolean { 21 | const emailAddressRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ 22 | return emailAddressRegex.test(emailAddress) 23 | } 24 | 25 | export const DEFAULT_USER: User = { 26 | id: 0, 27 | name: 'Guest', 28 | emailAddress: 'guest@example.com' 29 | } 30 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin-compat-253/com/asakii/plugin/compat/VcsCompat.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.compat 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.vcs.AbstractVcs 5 | import com.intellij.openapi.vcs.ProjectLevelVcsManager 6 | 7 | /** 8 | * VCS 兼容层 - 适用于 2025.3+ 9 | * 10 | * 在这些版本中,可能使用不同的 API 11 | * 注意:如果 API 在 2025.3 中有变化,请在此文件中更新 12 | */ 13 | object VcsCompat { 14 | 15 | /** 16 | * 获取项目中所有活跃的 VCS 17 | */ 18 | fun getAllActiveVcss(project: Project): Array { 19 | val vcsManager = ProjectLevelVcsManager.getInstance(project) 20 | // 2025.3+ 使用新的 getAllActiveVcss() 方法替代已弃用的 allActiveVcss 属性 21 | return vcsManager.getAllActiveVcss() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/services/ProjectSessionStateService.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.services 2 | 3 | /** 4 | * 项目级会话状态服务 5 | * 6 | * 提供项目级别的会话状态管理功能 7 | */ 8 | object ProjectSessionStateService { 9 | 10 | /** 11 | * 清理当前会话 12 | */ 13 | fun clearCurrentSession() { 14 | // 简化实现 - 清理会话状态 15 | } 16 | 17 | /** 18 | * 获取统计信息 19 | */ 20 | fun getStats(): Map { 21 | return mapOf( 22 | "activeSessionsCount" to 0, 23 | "totalMessages" to 0, 24 | "status" to "ready" 25 | ) 26 | } 27 | 28 | /** 29 | * 获取服务统计信息 30 | */ 31 | fun getServiceStats(): Map { 32 | return getStats() 33 | } 34 | } -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/listeners/ToolWindowStateChangedTopic.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.listeners 2 | 3 | import com.intellij.util.messages.Topic 4 | 5 | /** 6 | * 工具窗口状态变化事件主题 7 | * 8 | * 用于在工具窗口显示/隐藏时通知UI组件刷新状态 9 | */ 10 | interface ToolWindowStateChangedListener { 11 | /** 12 | * 工具窗口状态变化时调用 13 | * 14 | * @param isVisible 工具窗口是否可见 15 | */ 16 | fun onToolWindowStateChanged(isVisible: Boolean) 17 | } 18 | 19 | /** 20 | * 工具窗口状态变化主题 21 | */ 22 | object ToolWindowStateChangedTopic { 23 | @JvmField 24 | val TOPIC = Topic.create( 25 | "ClaudeCodePlus.ToolWindowStateChanged", 26 | ToolWindowStateChangedListener::class.java, 27 | Topic.BroadcastDirection.TO_CHILDREN 28 | ) 29 | } -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/kotlin/com/asakii/core/interfaces/ProjectService.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.core.interfaces 2 | 3 | /** 4 | * 项目服务接口,抽象IDEA项目相关操作 5 | */ 6 | interface ProjectService { 7 | /** 8 | * 获取项目根路径 9 | */ 10 | fun getProjectPath(): String 11 | 12 | /** 13 | * 获取项目名称 14 | */ 15 | fun getProjectName(): String 16 | 17 | /** 18 | * 获取相对路径 19 | */ 20 | fun getRelativePath(absolutePath: String): String 21 | 22 | /** 23 | * 打开文件 24 | * @param filePath 文件路径 25 | * @param lineNumber 行号(可选) 26 | */ 27 | fun openFile(filePath: String, lineNumber: Int? = null) 28 | 29 | /** 30 | * 显示设置对话框 31 | * @param settingsId 设置页面 ID 32 | */ 33 | fun showSettings(settingsId: String? = null) 34 | } -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/types/SessionTypes.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.types 2 | 3 | /** 4 | * 兼容层:JetBrains 插件侧使用的简化会话类型。 5 | * 6 | * 这些类型早期由 cli-wrapper 模块提供。随着重构撤销了该模块, 7 | * 插件端仍然需要占位类型来完成服务注册。为保持向后兼容, 8 | * 在此定义精简版的数据模型,避免 ClassNotFound 异常。 9 | */ 10 | 11 | data class SessionState( 12 | val sessionId: String, 13 | val messages: List = emptyList(), 14 | val contexts: List = emptyList(), 15 | val isGenerating: Boolean = false, 16 | val selectedModel: AiModel = AiModel.OPUS, 17 | val permissionMode: UiPermissionMode = UiPermissionMode.DEFAULT 18 | ) 19 | 20 | /** 21 | * 会话更新事件(精简版)。 22 | * JetBrains 插件暂时仅需要区分 sessionId 与是否活跃。 23 | */ 24 | data class SessionUpdate( 25 | val sessionId: String, 26 | val isActive: Boolean 27 | ) 28 | -------------------------------------------------------------------------------- /demo/TodoDemo.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.demo 2 | 3 | /** 4 | * TodoWrite 工具演示 5 | * 6 | * 这个文件演示了如何使用 TodoWrite 工具来管理开发任务 7 | * TodoWrite 工具帮助跟踪任务进度,让用户了解当前的工作状态 8 | */ 9 | 10 | // 这里将实现一个简单的计算器类来演示任务管理 11 | 12 | class SimpleCalculator { 13 | 14 | fun add(a: Double, b: Double): Double { 15 | return a + b 16 | } 17 | 18 | fun subtract(a: Double, b: Double): Double { 19 | return a - b 20 | } 21 | 22 | fun multiply(a: Double, b: Double): Double { 23 | return a * b 24 | } 25 | 26 | fun divide(a: Double, b: Double): Double { 27 | if (b == 0.0) { 28 | throw IllegalArgumentException("除数不能为零") 29 | } 30 | return a / b 31 | } 32 | 33 | fun power(base: Double, exponent: Double): Double { 34 | return Math.pow(base, exponent) 35 | } 36 | } -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/icons/claude-ai.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/types/toast.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Toast 通知类型定义 3 | */ 4 | 5 | export enum ToastType { 6 | INFO = 'info', 7 | SUCCESS = 'success', 8 | WARNING = 'warning', 9 | ERROR = 'error' 10 | } 11 | 12 | export interface Toast { 13 | id: string 14 | type: ToastType 15 | message: string 16 | duration?: number // 显示时长(毫秒),0 表示不自动关闭 17 | timestamp: number 18 | } 19 | 20 | export interface ToastOptions { 21 | type?: ToastType 22 | duration?: number 23 | closable?: boolean 24 | } 25 | 26 | /** 27 | * Toast 图标映射 28 | */ 29 | export const TOAST_ICONS: Record = { 30 | [ToastType.INFO]: 'ℹ️', 31 | [ToastType.SUCCESS]: '✅', 32 | [ToastType.WARNING]: '⚠️', 33 | [ToastType.ERROR]: '❌' 34 | } 35 | 36 | /** 37 | * Toast 默认配置 38 | */ 39 | export const TOAST_DEFAULTS = { 40 | duration: 3000, // 默认 3 秒 41 | closable: true 42 | } 43 | -------------------------------------------------------------------------------- /standalone-test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | application 4 | } 5 | 6 | 7 | 8 | dependencies { 9 | implementation(project(":claude-agent-sdk")) 10 | implementation(project(":ai-agent-sdk")) 11 | implementation(project(":ai-agent-server")) 12 | implementation(project(":ai-agent-rpc-api")) 13 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") 14 | } 15 | 16 | val mainOverride = project.findProperty("mainClass") as String? 17 | application { 18 | mainClass.set(mainOverride ?: "standalone.TestModelSwitchKt") 19 | } 20 | 21 | tasks.named("run") { 22 | environment("CLAUDE_API_KEY", System.getenv("CLAUDE_API_KEY") ?: "") 23 | } 24 | 25 | java { 26 | sourceCompatibility = JavaVersion.VERSION_17 27 | targetCompatibility = JavaVersion.VERSION_17 28 | } 29 | 30 | kotlin { 31 | jvmToolchain(17) 32 | } -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/META-INF/plugin-withJava.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | /* Path mapping */ 24 | "baseUrl": ".", 25 | "paths": { 26 | "@/*": ["./src/*"] 27 | } 28 | }, 29 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 30 | "references": [{ "path": "./tsconfig.node.json" }] 31 | } 32 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin-compat-diff-242/com/asakii/plugin/compat/DiffEditorCompat.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.compat 2 | 3 | import com.intellij.diff.editor.DiffRequestProcessorEditor 4 | import com.intellij.diff.requests.DiffRequest 5 | import com.intellij.openapi.fileEditor.FileEditor 6 | 7 | /** 8 | * Diff Editor 兼容层 - 适用于 2024.1 ~ 2024.2 (241-242) 9 | * 10 | * 使用 DiffRequestProcessorEditor.getProcessor().getActiveRequest() 直接 API 调用 11 | */ 12 | object DiffEditorCompat { 13 | 14 | /** 15 | * 从 FileEditor 获取 DiffRequest 16 | * 17 | * @param diffEditor Diff 编辑器 18 | * @return DiffRequest,如果无法获取则返回 null 19 | */ 20 | fun getActiveRequest(diffEditor: FileEditor): DiffRequest? { 21 | return if (diffEditor is DiffRequestProcessorEditor) { 22 | diffEditor.processor.activeRequest 23 | } else { 24 | null 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/kotlin/com/asakii/claude/agent/sdk/plugin/types/SessionTypes.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk.plugin.types 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.flowOf 5 | 6 | data class SessionState( 7 | var lastFileSize: Long = 0, 8 | var lastLineCount: Int = 0, 9 | var lastModified: Long = 0, 10 | val messageCache: MutableList = mutableListOf(), 11 | val messages: List = emptyList(), 12 | var isGenerating: Boolean = false 13 | ) 14 | 15 | sealed class SessionUpdate { 16 | data class NewMessage(val message: SessionMessage) : SessionUpdate() 17 | data class Compressed(val messageCount: Int) : SessionUpdate() 18 | data class Error(val error: Throwable) : SessionUpdate() 19 | } 20 | 21 | data class SessionMessage( 22 | val content: String = "", 23 | val timestamp: Long = System.currentTimeMillis() 24 | ) -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/startup/PluginStartup.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.startup 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.startup.ProjectActivity 5 | import com.asakii.plugin.config.PluginConfig 6 | import com.intellij.openapi.diagnostic.Logger 7 | 8 | /** 9 | * 插件启动活动(动态插件兼容版本) 10 | * 11 | * 使用 ProjectActivity 替代已废弃的 StartupActivity, 12 | * 不修改全局系统属性,支持动态加载/卸载。 13 | */ 14 | class PluginStartup : ProjectActivity { 15 | 16 | companion object { 17 | private val logger = Logger.getInstance(PluginStartup::class.java) 18 | } 19 | 20 | override suspend fun execute(project: Project) { 21 | try { 22 | // 仅执行必要的初始化,不修改全局系统属性 23 | PluginConfig.setupEnvironment() 24 | logger.info("插件启动初始化完成") 25 | } catch (e: Exception) { 26 | logger.error("插件启动初始化失败", e) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/components/tools/CompactToolCallDisplay.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | 24 | 38 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/claude-agent-sdk-demos"] 2 | path = external/claude-agent-sdk-demos 3 | url = https://github.com/anthropics/claude-agent-sdk-demos.git 4 | [submodule "external/claude-agent-sdk-typescript"] 5 | path = external/claude-agent-sdk-typescript 6 | url = https://github.com/anthropics/claude-agent-sdk-typescript.git 7 | [submodule "external/claude-agent-sdk-python"] 8 | path = external/claude-agent-sdk-python 9 | url = https://github.com/anthropics/claude-agent-sdk-python 10 | [submodule "external/opcode"] 11 | path = external/opcode 12 | url = https://github.com/winfunc/opcode.git 13 | [submodule "external/acemcp"] 14 | path = external/acemcp 15 | url = https://github.com/qy527145/acemcp.git 16 | [submodule "external/openai-codex"] 17 | path = external/openai-codex 18 | url = https://github.com/openai/codex.git 19 | [submodule "external/java-sdk"] 20 | path = external/java-sdk 21 | url = https://github.com/modelcontextprotocol/java-sdk.git 22 | -------------------------------------------------------------------------------- /frontend/src/constants/messageWindow.ts: -------------------------------------------------------------------------------- 1 | // 消息窗口配置 2 | export const MESSAGE_WINDOW_CORE = 600 3 | export const MESSAGE_WINDOW_RESERVE = 200 // 上下各预留 4 | export const MESSAGE_WINDOW_TOTAL = MESSAGE_WINDOW_CORE + MESSAGE_WINDOW_RESERVE * 2 5 | // 历史分页默认尾部/每页大小,使用窗口总长 6 | export const HISTORY_PAGE_SIZE = MESSAGE_WINDOW_TOTAL 7 | 8 | /** 9 | * 首次加载的消息数量(尾部100条) 10 | * 设计原则: 足够少以快速加载,足够多以提供初始上下文 11 | */ 12 | export const HISTORY_INITIAL_LOAD = 100 13 | 14 | /** 15 | * 懒加载每次加载的消息数量(50条) 16 | * 设计原则: 小批量加载,减少单次加载时间 17 | */ 18 | export const HISTORY_LAZY_LOAD_SIZE = 50 19 | 20 | /** 21 | * 自动填满视口时的最大加载量(200条) 22 | * 设计原则: 避免超宽屏/4K屏幕时无限加载 23 | */ 24 | export const HISTORY_AUTO_LOAD_MAX = 200 25 | 26 | /** 27 | * 懒加载触发阈值(距顶部的距离,单位px) 28 | * 增大到500px让用户更容易触发 29 | */ 30 | export const HISTORY_TRIGGER_THRESHOLD = 500 31 | 32 | /** 33 | * 懒加载重置阈值(距顶部的距离,单位px) 34 | * 设置为触发阈值的1.5倍,防止触发后立即重置 35 | */ 36 | export const HISTORY_RESET_THRESHOLD = HISTORY_TRIGGER_THRESHOLD * 1.5 37 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/icons/claude-code-simple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/src/components/input/AtSymbolFilePopup.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 39 | -------------------------------------------------------------------------------- /frontend/src/services/rsocket/errorCodes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 自定义 RSocket 错误码 3 | * 4 | * RSocket 协议允许自定义错误码范围: 0x00000301 - 0xFFFFFFFE 5 | * 我们使用 0x00010000 开头的范围作为应用自定义错误码 6 | * 7 | * 注意: 这些错误码必须与后端 RSocketErrorCodes.kt 保持一致 8 | */ 9 | export const RSocketErrorCodes = { 10 | /** 11 | * 客户端未连接错误 12 | * 当 Claude CLI 进程意外退出或连接断开时抛出 13 | * 前端收到此错误码应触发自动重连 14 | */ 15 | NOT_CONNECTED: 0x00010001, 16 | 17 | /** 18 | * 会话已过期 19 | */ 20 | SESSION_EXPIRED: 0x00010002, 21 | 22 | /** 23 | * 认证失败 24 | */ 25 | AUTH_FAILED: 0x00010003, 26 | } as const 27 | 28 | export type RSocketErrorCode = typeof RSocketErrorCodes[keyof typeof RSocketErrorCodes] 29 | 30 | /** 31 | * 检查错误是否是需要重连的错误 32 | */ 33 | export function isReconnectRequiredError(error: unknown): boolean { 34 | if (error && typeof error === 'object' && 'code' in error) { 35 | const code = (error as { code: number }).code 36 | return code === RSocketErrorCodes.NOT_CONNECTED 37 | } 38 | return false 39 | } 40 | -------------------------------------------------------------------------------- /ai-agent-sdk/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | kotlin("plugin.serialization") 4 | id("java-library") 5 | } 6 | 7 | group = "com.asakii" 8 | version = "0.1.0" 9 | 10 | kotlin { 11 | jvmToolchain(17) 12 | } 13 | 14 | java { 15 | withSourcesJar() 16 | } 17 | 18 | dependencies { 19 | api("org.jetbrains.kotlin:kotlin-stdlib") 20 | api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") 21 | api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") 22 | 23 | api(project(":claude-agent-sdk")) 24 | api(project(":codex-agent-sdk")) 25 | 26 | testImplementation("org.jetbrains.kotlin:kotlin-test") 27 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") 28 | } 29 | 30 | tasks.test { 31 | useJUnitPlatform() 32 | } 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkcM4ACgkQtGPwyPeP 4 | qI+XHRAAkbD6WCacS4yAZDwNMP0NUMxbN8DZ6VzsQ1qAkEzJaNYlcrghh+Xs5s5D 5 | tXv85riwoCA/abQAjZtSyBTL3cB6Z0lJuZnAjEKskLgfTMMq/V+eO0k93PiJse++ 6 | 5DHGgCVJOvNBcvo7KHDcQUXA5UDoLWjUZZMxI7NQ2YT8v9Yv3l71QeUcm7e+N0sU 7 | yUI9CqaNAzIW1rgtGThmqkKbgJR4IETxPdv+7l/ytwnBwhMYfKcZ5KTv5OerHdQr 8 | mIKGOrgiplyiNXuXJixZu0dRehgfuHzwlpajQ7el/f/iODSb7cSBRGrnVOHHSmhJ 9 | qphWhvIznRxV7kEo06OM2WK5xkWovWbrLQqoujR1Xb3tOLiZcwXX+3njRj2HxsiR 10 | kdQZ37SijzUb2AtzMIz3e7JYdYacMW/NoAfoZ3LeEGdAGUVVE63BKdoQREqHvGiV 11 | TTmdYUCCVVH5D+PJspxbu8H7bx1A35UElYVvIdIH5npEoroViDZCtzNacY3hAk/D 12 | /VyKyonIoJ/B5Y4dNCl78PXiusc/Jg/fl5z7Sxo37yylToek/0e62crOV097ygVe 13 | FTChM5ObHBSzZ/K64CWgWnoNshuGSn5DwzlJS6893uHyWWY32949yLZBRAD3NP7K 14 | xrVDyXaQSnEBEMM2lGOHs9YdaFuwrqVG7QczkdExpgu2rYFuIMg= 15 | =wYP9 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0.pom.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkcM8ACgkQtGPwyPeP 4 | qI+Y/g/8CytxvznwjUiDazKpHVs7dC8ZiCZapWmPMF9M5ny4OMeXYaVhOJuLunV2 5 | aH3a4CpHbPfcyGcFDVX+YeLcqXEc03aqxB4xdZXXg+j8SUjFPm6an/smdl+2Rz3K 6 | YnoTUiCEEBOmHwkPXwF4n86UhkWjCdndubFD5ly7UnGcF54JSDgWvuVAbIkRKviX 7 | csacB38khEaIVNENYOxY+JaSVL2OxGdU+hZfdqgAV+X0OaqeCs5L9ilHJnUo7u+q 8 | Bh2prlGEFVZnMwkNBG7tzFfyAZ9WJExkiLTsHwql8NqjYeBut1IN5QIDRvgugiQe 9 | Pla+0qxCydBt9Oll54qkM99154f0GaL4k0pOOQXoB1IC6LzCXGwXzNc28tCulPbY 10 | mNpYQKEzhNgcZFCdHyi09XejQommN2Zf3ecRBBZMQBB16XtLoe0+bnx3hjPRDiL/ 11 | cVq8FE18WBA29uvlne6qcXtN/YU7Uvh7sw9szpM71LyDk78oIkTypYgF4nBPuiFY 12 | 5JZWcGlQsC3+1DWEmhH7ckzcMDNkFHck9xrZ7G158GKUL408G9hPxr4ZB+tMms28 13 | fNSHFxSKF9iIdq0uohMlnDmgCQqX78Oa0ui25WEg5E3pJRHnqkwa/2Pl/r/cYhQX 14 | NXSgUSk9BPcQST6xhENmafJETAEi2CmVErOVl4lgrBxy4OYzBE4= 15 | =DRYK 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /ai-agent-server/demo-tools/example.ts: -------------------------------------------------------------------------------- 1 | // 这是一个示例 TypeScript 文件 2 | // 用于演示各种文件操作工具 3 | 4 | export class UserService { 5 | private users: Map = new Map(); 6 | 7 | constructor() { 8 | console.log('UserService 已初始化'); 9 | } 10 | 11 | // 添加用户(带验证) 12 | addUser(user: User): void { 13 | if (!user.id || !user.name || !user.email) { 14 | throw new Error('用户信息不完整'); 15 | } 16 | 17 | if (this.users.has(user.id)) { 18 | throw new Error(`用户 ID ${user.id} 已存在`); 19 | } 20 | 21 | this.users.set(user.id, user); 22 | console.log(`用户 ${user.name} 已成功添加`); 23 | } 24 | 25 | // 获取用户 26 | getUser(id: string): User | undefined { 27 | return this.users.get(id); 28 | } 29 | 30 | // 删除用户 31 | deleteUser(id: string): boolean { 32 | return this.users.delete(id); 33 | } 34 | } 35 | 36 | interface User { 37 | id: string; 38 | name: string; 39 | email: string; 40 | } 41 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkeRwACgkQtGPwyPeP 4 | qI/YZRAAgWJKFZr9bILpBsnjfnR/PWyOXraq9MCxErZfXJeLIQP54GVOVsH8uq+s 5 | MEifj73RFLg6RRUeM8EgJayc6BQQR+pmZgv2gjKNEPgMAl90LnlM2WPs4HdkK2j9 6 | KH7yQY+fAugYsnetzxHQklKGUisOBK08KrehNcGBwCKOUMV4MTMegw7qIk2YoJZv 7 | CU70NM/4ZUR1flJ60b+KmwRibj4nkcQ8bALSyOXPuZnljKDAk85vzpAZKjjjKmGC 8 | brXVxvnESMfoF3gOqcLtEvGqptTyxVgEzs2ULGEEza1Akq0audR+iNJjOag6XMjY 9 | 9OoUHAr91OzeUrZJtjvp+v2T5p2TyHNond1pqJgvIfXaaB/U9D6xN9hfJms7Sayn 10 | nLcOnsVlZXF/xFdk82Bq6IkUUAlqNspM0E6f8DtaICGNydAQgN+/EuQYMe9JlY8k 11 | YQDnbzkpTqamlrxc/QjqYmilmaIl33NuTW+FfkeVmsdO85dwRnkOWA1lxoc+lywc 12 | E1hWBAYW+2ejH3lhd5rrcm7sLpnuwnyAukmvDT41HMFepKXJWyYeJx2yp0YirIoo 13 | iMVQNupNDmxBxZEANRFCamLSkNuEm0fxUTQtT9PHbVrFUM1XFm01+EcLST6/+2Yy 14 | otZ5Z1cZQUZwBFGe+RFBqKLDCNI1WF+Xum0a0RLdqqWEVWt6jh8= 15 | =pGvY 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0.pom.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkeR0ACgkQtGPwyPeP 4 | qI8sMg/7B9VpD2+FVX2gqP7BuCagdlFbmIRPeX6FGph4VNX8uBXidl/Kx5d8zQ4T 5 | LYWZQyZfV/vucB+Rbj20gqvkfp6o6vwmFshhoheKgDqmZp2oXAQo4EM1xiw41BdV 6 | lvf/WP5IlScru3lxV+FH3zpLm1RKerFxHeBbadYkogA1lSHkK3oALOa/T12iF3Nm 7 | RJ+WiHU2H9n4sGOIGEkOum75VdKeoijwKEEUDQZu/JZhPf00FnewwXRPQrFkqKjQ 8 | Nsz2OTyCtJsTGHTLaJz7A8m4kR37MTO1ls+GrDVyPhEsL4+DjolMzcEuOlnfQJfe 9 | jgypULc6u2GdavvJwdGrBIz3DFLMlt7c4M4T4vd9JJapkBo0SZzo+4qwPJZQAUzk 10 | k/UyD6SBFebdATBP31KdihGy2MA7cvifObQG9GUj0O+tmzgQroyiHxnP1H6hDG+M 11 | HHuW1u7C2STkkbNC2EJ0vfGkmkJrnNfW7YIxo1t6i/yNEbVD1cDMvn1jb1d2xN4k 12 | ouS2BfBEqVmNtLbM854N13ukKCTeSU20BWtFmXE14fINpsN/x4LpaqOus9Qhz63R 13 | WhRwSYHieJAFb0YS6puN7Y/SRSS5IiheLO5BOa6zW8I0TyWtWehflECM91srhViw 14 | 2wFKru2RLW2c8gs0/ATIP+CNI51zqCSrhoAHXTbZ4H2JPjWcA9E= 15 | =FKsb 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-javadoc.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkeRYACgkQtGPwyPeP 4 | qI90MBAAt0buVw4yMf1CwC319UAt7WkQvBmPuVg7qOIu5tmnj45DYtpzsmU9GcuM 5 | SSRffRSPwuE+iimktvOukb2dOzDw3G9FE9sSS8gAIPdFBrIMXcGYSj2wPpSfeXwE 6 | g3U2uxRDQ0xsjhGO92cQUA/dAbJViObE/Dz6HhNb/uDbEvqODFbhbpAFQQmKZDLc 7 | GcUb6orLjyVmQ3IBC5KYB8jfn/YdZ1JIrDVSgE4MbcbSzwdN1g0lxAAaKBZaw0H5 8 | jCPcitUALuyXyLIGr+NGplRbcZj2S6UfhFAGjbA680fRuFdMF9rxnYrHHNEdeOFf 9 | LCZU2qvuorK4fhQ7/XAV2ijsAc2MkpMlgHctVcttVoo6yNDiaeWxdxz3qnM2aSr/ 10 | T8KqwMpou2CHcFLOIzG2Y6XHrIj69dh63OH+aCPMs5Irm9fq12DWfZW1xkdjfaW7 11 | 87j9nuVjtTbxDkDjERQTDvI2dCzbUXAmJX0tAV7L+Mmym8EGxCVUfG7qAM0S0zj+ 12 | mo3OVzDQmdOMmDEDuRm0i0dxvQor2IOylCufkJ9D/9xEeGbIIrXxSFFpk8P2QYby 13 | 4FjQ80op2pKLLO8ra4JSjt9AursKnNXAQs3c1vCi88RENDJ44f99HRWOUPluIYIy 14 | GMx61BjYcnFmiqF68L3a4GYXyBGfiWrPGCdLTpz0HWxJE+3xAIM= 15 | =H/qi 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-java-upload/com/asakii/claude-agent-sdk-java/0.1.0/claude-agent-sdk-java-0.1.0-sources.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkeRsACgkQtGPwyPeP 4 | qI8J2Q/+PI+pIcjQhjnuzQLTavl0eZAJ5CcJeL0pTcG1JSmps6KN22l21sZf9N1i 5 | 48QrwpapHfy7qG5QniD4x6l8ZJnt+W71+N5Fva7ZfF0xbCXrCqSr2lAvLimb0+dD 6 | 4o/GlinNmRNAbWzKYlftK5Kq4IEM479M/EMhOoVdyyFRbpjED/vJp5gHV7wJdV/2 7 | CKmSlmRcx65ECoSNQ4rjUueBVdCFXaJXwrlnEd8LlCAjF7i9UA57rtidMTXChRQ0 8 | /oJT5SAi5I/gG54Ksr1J6Uvup8WaGqt6kQCdtI09HUWodi+3WudvzHTj4pcF9bWh 9 | 0yvmR+zz9f4u1Vhvj1XzHPFkxyuMgCrCeWMJD4X6rGUPrebXcEDx7MNMRL/xgX+q 10 | bGK9kRvgASglmNYXDCdWoWJDNuWmLg7HNDyaiQS6MgrAr07cK6v/7SW0/94570ys 11 | SbfK3dCywonfoj7HhW0zjuiK266nlL4qwVKSGt05UzQjS3++RVyxwDsj7A3xKRwX 12 | iUCuqG4Zw3V7yiAduyql6RzfR+MOetnme1TpE7atEOKG2qVCqHUHVFrmhVWLfSlV 13 | 6mOQYO7b8ru00APIqrBnphnNTKY2M1mtsOmcSiN7XKLik4tJ7CQ0g5xZUCrnJ8S3 14 | vTKtU2tjfm3ABb/4WZMLnuHMsswxYecad8HelBj0BpdW+FNpiYs= 15 | =S3X1 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIyBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkeUMACgkQtGPwyPeP 4 | qI9C0A/2J/UF/kboujGx0Yg7RmaUqVn01XWaF43yWb+7gWBgExw0RU7z5B8jCPqn 5 | laMDN8NRyDuE9dLkmGIQjQOJ5dTTFJ8YGcf9EypXkIrXJqlPeh/ijQkbBKV3sxjX 6 | WuU0lerbCV6i6zn1HWV/pYRho2Yog873C5wsTuJnrXkEm96/y4JqzRyv0IVnXgJL 7 | IpQ+sqXk3g42FO7+iUN305QJ2Pti3Z2Hip/uLB9VAbVZ83eHnKknPjD1Egg6FPjv 8 | BQXrclrO1lEeIXPcT9WAz3AqBE/z3g4epkGhX5M9qY5IQMvrWEu8FSxrmlJq0uFt 9 | eVZVMAUPQVxxB1gYMhIhX1m5Yy+o1VM4GGXguNzv+wgbMoCS/1WteQUNZUz0AY7w 10 | lT4McZumNszTR1Bb3ROlBJmZzfEkjB06oUoh+sKI/izEd/XvLl6mmMGA0mBjbUW3 11 | qNEKK/OJlHi77l85xBtFAIYyhkIRUeJwrU5y50GKqplgX0vPKJshUoC7HFOtbmR8 12 | HpTONfkISrhMpdzAsgfF1J1Kj5iYZiSaYAbC6LseFpT4wn7NDS1W48ple6vjPyot 13 | jKCUqewlfQu69lzlNrR6shAfNwG2Z3zBq+KuhgVCAfNqbZVmKw9rZMNNpW2nAtcF 14 | /9gSrp9WCpaU8RycQ3oiJo3lO4a5FcexGyA0soAde3YZEOwWNw== 15 | =xBUS 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0.pom.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkeUQACgkQtGPwyPeP 4 | qI8x1BAAoDel8u8pKzFXjLmQP30CnHnYobbW/CW8bgWruwVUqJabZeOkOm5/srGm 5 | 4UGI/Efki4aVgMw+VaKbl+CTHx9MmbIkBdRIOdJeJ8+nraYwbgadITjIRSyAlGxK 6 | k/bimfkVeLBwiYy3LQDTNQBI0f0Pd/rPRyUDxGrAiRSyRqAca7+MMGSX9DOUFTa7 7 | u64f4B3uLpkZmP+WRsruBth2IwkciojVUjlcw6OvoGcr7CayoMhxHBXVDyCT4QTG 8 | 32KMvxXoFdddCkgxw1RGz7LpCZvy7N9R7nc1xZthw+vPcyRvqtYGbCwSzrmN6VrJ 9 | UePSVMO66xf6GqbN7DBJLAYT6k2rKpcbCgEWDDuTygfodnyhMmNXdAhiccZ7dIt4 10 | rHkvaKZw0HDoxbSTi5nqRMrVIOd9bvGdAr0d5ra2xNRC9ptF2dkBsyv7/IUaTGik 11 | Ca6ieZ/iI7L6qXCZbzQLCR+e8yQ5DZXuEOjcMWKILa9I50nFIJeMUSQew2Y7AQ1x 12 | SXhpCOu6VM3tno6vFQ2oS1YLPj1Qcf+qvTVI4KXrv7Nidt4tmM5XfglEQdr7e0ad 13 | b42ZMJw6dO/D2k74juBqn/4nX9mqmfXQbtVyibiatEVu71QoY9OTRyibjFrdVAhh 14 | XZcsaeJkgXPUyX2qs4/fxq08y6kVMdUUhWAFC89ygKc+ogg2iM0= 15 | =oDyv 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-javadoc.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkcFgACgkQtGPwyPeP 4 | qI/gUQ/+PQIulxAq4PhQQYZtEkeCGf4yHN7dSh0VlcaqYVi3tWnP2sOVn31XRjzI 5 | w09EjOwj3mqVUoBKQeiscNMQSY/8dG8bW9q92mC+2eHLgqlrScmwJURxILuZjSkE 6 | 5FuHhV9WG07OS9TO/wEa2onkwM92GSl69651nJVCSEyq0k5Ty8XtX1FRBzdoPZyq 7 | RuIAMdBwCzPUL/0V9dFj62bYDvrEGjRiXj88LFzi1K8IZemhQxVQlXAdHiYh7kHz 8 | eRS1YAW+gGBIrNhWt5txVMPxVcuW0lBypuhQpRb9Qh9QPQ9r34sgQDQ9anEOiW1d 9 | aIM8V4xcdaIWmXQWUMcy/OUJ+QCoHJr4ufho55nUTM1Fy91Q8/bCCCcCZIM7CEKM 10 | LTY2kPcvjSStfGtLQ3j+S/N+v0sm1Ezb1rSxjEkiV/V2AH8mvj5Da35xj98HAsvl 11 | 6kxCAolu+9Gdlhk23qOWnqAxOaIZEa64sXRml6jf0evJHli+DCSC40hosLOWA2lM 12 | 2KOE9W+DI1y6fXeI34v72D0j0pkaRSyse/aFZ12UoSbwa/ojXKH7Vgl4kSNZXBk5 13 | HgT08685d26TuywtTeHL44iUwlOji+aFFpuBROYFdh/qBvXi1TOpREzsI3FxAcft 14 | a9D30g0LInT2Fw6jxyiCMORd8gTsPtFofMGQ+mqvmZxcEtHPuq0= 15 | =BmGE 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/codex-agent-sdk-java-upload/com/asakii/codex-agent-sdk-java/0.1.0/codex-agent-sdk-java-0.1.0-sources.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkcM0ACgkQtGPwyPeP 4 | qI/hpA//XRVTekF0l6o6xZHiyG5b/nP6uuDCerD0f91Y9Lp8FYC8xlWE/zPao4qj 5 | im0I7vvVWmu5zHW6R1TvbVBv6n0enL4PVhO0u/vzuTrzpa2LyZkz4G0pVP/o0Lr4 6 | v99svsDEWdkGDxu9AX6Ib84e5wOfFrVGo87VURrVSkKAfqr9Orjq4wrU/biRq/kh 7 | r7aFar89tv63aTI9IjQVMUwTcBEp1pIacw2DfHYQxWmP8BGHj0TRFO/TOtBMgbpZ 8 | Hx/keNWx0hZaXI+ez0lekONMgO2aM03TzplwoQIYk4L5mOx7cI46d9cF3BRNR68L 9 | v/M3mmg+wrHGBEJ45ERPk2ry2GMJvZf7p8xsr4OUEUr634gha4HuNaFt8Ul+Lj6l 10 | 4X9VXVlVEQRIeUlY1+E40aw0uRjUsdR0I5UlQ2rkW4xLJYxEoMmdBftbCiwYf44o 11 | b/5sIkB25AQwXt4TyeUiKkpz5wO/tV2rpUYVvRdmSj9MYzpFkfcn552EEDgweSsR 12 | H3k+TVefWLCvsIhtFnG5KUKdT+OuZEgCladaXc5rWrhPFeKzWccmiT04PtmmdXOV 13 | 4yP9+y8qNGLSeQ2CYZFLg5j6Y84jn4V3ZO9+XqkQ4alUetYPe55v7CAZSJTAoLoC 14 | hGMe1laDx0P7nKn9CyQDci2xaWUMypeLRqHOeJowE56Nnt+P0No= 15 | =6brl 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/server/mcp/TerminalMcpServerProvider.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.server.mcp 2 | 3 | import com.asakii.claude.agent.sdk.mcp.McpServer 4 | 5 | /** 6 | * Terminal MCP 服务器提供者接口 7 | * 8 | * 由于 Terminal MCP 需要访问 IDEA Terminal API, 9 | * 实现类必须在 jetbrains-plugin 模块中创建。 10 | * 此接口用于解耦 ai-agent-server 和 jetbrains-plugin 的依赖。 11 | */ 12 | interface TerminalMcpServerProvider { 13 | /** 14 | * 获取 Terminal MCP 服务器实例 15 | * @return McpServer 实例,如果不可用则返回 null 16 | */ 17 | fun getServer(): McpServer? 18 | 19 | /** 20 | * 获取需要禁用的内置工具列表 21 | * 当 Terminal MCP 启用时,可以禁用内置的 Bash 工具 22 | * @return 需要禁用的工具名称列表 23 | */ 24 | fun getDisallowedBuiltinTools(): List = emptyList() 25 | } 26 | 27 | /** 28 | * 默认实现(不支持 Terminal 集成时使用) 29 | */ 30 | object DefaultTerminalMcpServerProvider : TerminalMcpServerProvider { 31 | override fun getServer(): McpServer? = null 32 | override fun getDisallowedBuiltinTools(): List = emptyList() 33 | } 34 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-javadoc.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkeUEACgkQtGPwyPeP 4 | qI/VUxAArG7Ao7tt+zxwqGwciYiy3lDXf87J8FA9sbQKseGnLu2g7g1uekdl4g/C 5 | INyosIgkE4LGC9DrWt5KfQyHNsR1IZ6W2Pnx0Ebi2pwH5KUttjJjtGGovCGz67P4 6 | g5IkkhUzE/rwrEzv0S7NXr6hfsWFgX5EIiYkUfvFMs0UvS2z4hlMi2O2r2OVyEuU 7 | qHi0se5Ilj3y6UM4IDXxUMK5vGL+ucKDdAnu6SoWmMW1fNYhTt6ix3jkfiUz3X7l 8 | avN6HKYow9wTeUIeroXF7PXc9/oiPF8hcQsLQpSHWxVI49Qo0IxDUCJAiOHw+ESX 9 | /z41H2AJPoPeAezOqGK2QQeshffu4UORFLKjdd0Og3kEy1EIR58qXLEN0xQ4E4DX 10 | +rxm67Rqte8kd+kpsujixUHlheA4d5H02J4tcTrudVxtfUp2V8T0GO78+U2JMswT 11 | 3MMgxNM9D+wNjxVMLXaEjMnbBLOzylRLzIkMD87tBewL3EqIqY3vq4n+JDFdwuoI 12 | AOdmPDT5Pf6UEwPCgcqb//ViMK47LSBMcdb/egaDdy/W61QPoogoGbBTyCJpEGAo 13 | g5PE1PxyfVSsrS4sj0vc7bNEJovF8NDcJXz0UUlbL6YWw15lNvYq3aGTHDQxGn2E 14 | 9gmhePvH2mxpGbSzJZFiwS6GyQhGDUB+I1Ge0CO4VSVArUTzNTU= 15 | =EFLX 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /release/sonatype/0.1.0/claude-agent-sdk-kotlin-upload/com/asakii/claude-agent-sdk-kotlin/0.1.0/claude-agent-sdk-kotlin-0.1.0-sources.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | 3 | iQIzBAABCAAdFiEEdpeD9U6KzckP7WzctGPwyPePqI8FAmkkeUIACgkQtGPwyPeP 4 | qI9vcQ//QcHRBatnOAmXrX3zEXJHDtFRds7IYmMGV5O2r78GXj48BesK2/E7tmcF 5 | DdyO1QRn2kKCMyIe0P7K/I74XAqo3i5fU1MIJw695rykH1y/EYdEjxLcvtqbmCMZ 6 | dpmSfgSAgZdsIl8E9TopSmP5sT2PwBcPl2ONT9IDAs1fT3FTz6qicVT2WhyVGDzd 7 | VzbdQ1p+9okaNkyLoRvQOMXkyAXjV6Qqp0V8zzcfZgVzr2FBK+iYwts+M51iT0xy 8 | lYr1fK3agi+uj9/yxILpYtiEIn+xOzXoHAEL4Qa87xYr7F2dgQsOgdh0uymbUWbu 9 | fYlZFZI+ZhHP2DJjG/qevBAdJJymH80CwSqj+Ozd2CERJCMwI+QnbY8xomZSxKZT 10 | SMLAtJ7qUp4fabESnDNP8A/b2DvPR8eJ3pQQHL+liE5H/SEEzaIu5Sek40e1Z5vZ 11 | 3Jk8aofzb2vwbj2KH2jKLh9ORr5rD0sarDUEEYDtHuO1Fb4mvxTAaIPPugCiKios 12 | BezmV7w8OJCB0Z3q5guFW7N84wpiq5iL+ISIqc462S3P8e/Fqx4jjGedJYjILex2 13 | WwCF4xHLhmf+jNmKdzQeEcOt+ydULHpIDLNvA9jYVUkEFccHZlSWX9Xbhud6dRqX 14 | ASMir02nvP1XGXmp8AY8z6NO03WdblW8vbWQ9pVcNH9kGpwfS1g= 15 | =Y6GE 16 | -----END PGP SIGNATURE----- 17 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/server/mcp/JetBrainsMcpServerProvider.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.server.mcp 2 | 3 | import com.asakii.claude.agent.sdk.mcp.McpServer 4 | 5 | /** 6 | * JetBrains MCP 服务器提供者接口 7 | * 8 | * 由于 JetBrains MCP 需要访问 IDEA Platform API, 9 | * 实现类必须在 jetbrains-plugin 模块中创建。 10 | * 此接口用于解耦 ai-agent-server 和 jetbrains-plugin 的依赖。 11 | */ 12 | interface JetBrainsMcpServerProvider { 13 | /** 14 | * 获取 JetBrains MCP 服务器实例 15 | * @return McpServer 实例,如果不可用则返回 null 16 | */ 17 | fun getServer(): McpServer? 18 | 19 | /** 20 | * 获取需要禁用的内置工具列表 21 | * 当 JetBrains MCP 启用时,可以禁用内置的 Glob 和 Grep 工具 22 | * @return 需要禁用的工具名称列表 23 | */ 24 | fun getDisallowedBuiltinTools(): List = emptyList() 25 | } 26 | 27 | /** 28 | * 默认实现(不支持 JetBrains 集成时使用) 29 | */ 30 | object DefaultJetBrainsMcpServerProvider : JetBrainsMcpServerProvider { 31 | override fun getServer(): McpServer? = null 32 | override fun getDisallowedBuiltinTools(): List = emptyList() 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/composables/useEnvironment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 环境检测 Composable 3 | * 4 | * 提供运行环境的全局状态管理 5 | * - IDE 模式: 在 IDEA 插件中运行(通过 RSocket 与后端通信) 6 | * - Browser 模式: 在浏览器中访问(使用默认 URL) 7 | */ 8 | 9 | import { ref, computed } from 'vue' 10 | import { ideaBridge } from '@/services/ideaBridge' 11 | 12 | const bridgeMode = ref<'ide' | 'browser'>('browser') 13 | const environmentReady = ref(false) 14 | 15 | export function useEnvironment() { 16 | const isInIde = computed(() => bridgeMode.value === 'ide') 17 | const isInBrowser = computed(() => bridgeMode.value === 'browser') 18 | 19 | async function detectEnvironment() { 20 | if (environmentReady.value) return 21 | 22 | await ideaBridge.waitForReady() 23 | 24 | // 使用 ideaBridge 的模式检测 25 | bridgeMode.value = ideaBridge.getMode() 26 | 27 | environmentReady.value = true 28 | console.log(`🔍 [useEnvironment] 环境检测完成: ${bridgeMode.value}`) 29 | } 30 | 31 | return { 32 | isInIde, 33 | isInBrowser, 34 | environmentReady, 35 | detectEnvironment 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/adapters/ProjectServiceAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.adapters 2 | 3 | import com.asakii.core.interfaces.ProjectService 4 | 5 | /** 6 | * 项目服务适配器 7 | * 实现 ProjectService 接口的简化版本 8 | */ 9 | class ProjectServiceAdapter( 10 | private val projectPath: String = "", 11 | private val projectName: String = "Unknown" 12 | ) : ProjectService { 13 | 14 | override fun getProjectPath(): String = projectPath 15 | 16 | override fun getProjectName(): String = projectName 17 | 18 | override fun getRelativePath(absolutePath: String): String { 19 | return if (absolutePath.startsWith(projectPath)) { 20 | absolutePath.substring(projectPath.length).trimStart('/', '\\') 21 | } else { 22 | absolutePath 23 | } 24 | } 25 | 26 | override fun openFile(filePath: String, lineNumber: Int?) { 27 | // TODO: 实现文件打开逻辑 28 | } 29 | 30 | override fun showSettings(settingsId: String?) { 31 | // TODO: 实现设置显示逻辑 32 | } 33 | } -------------------------------------------------------------------------------- /ai-agent-sdk/src/main/kotlin/com/asakii/ai/agent/sdk/capabilities/AgentCapabilities.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.ai.agent.sdk.capabilities 2 | 3 | /** 4 | * Agent 能力声明接口 5 | * 6 | * 不同的 SDK 实现返回不同的能力集合, 7 | * 调用方可以根据能力来决定是否显示/启用某些功能。 8 | * 9 | * 设计原则: 10 | * - 静态能力:编译时确定,运行时不变 11 | * - 能力发现:connect() 返回时包含能力信息 12 | * - 安全调用:调用可选方法前应先检查对应能力 13 | */ 14 | interface AgentCapabilities { 15 | /** 是否支持中断会话 */ 16 | val canInterrupt: Boolean 17 | 18 | /** 是否支持动态切换模型(不重连) */ 19 | val canSwitchModel: Boolean 20 | 21 | /** 是否支持切换权限模式 */ 22 | val canSwitchPermissionMode: Boolean 23 | 24 | /** 支持的权限模式列表(前端可用于渲染下拉选项) */ 25 | val supportedPermissionModes: List 26 | 27 | /** 是否支持跳过工具权限认证 */ 28 | val canSkipPermissions: Boolean 29 | 30 | /** 是否支持富媒体输入(图片等) */ 31 | val canSendRichContent: Boolean 32 | 33 | /** 是否支持思考功能 */ 34 | val canThink: Boolean 35 | 36 | /** 是否支持会话恢复 */ 37 | val canResumeSession: Boolean 38 | 39 | /** 是否支持后台运行(将当前任务移到后台) */ 40 | val canRunInBackground: Boolean 41 | } 42 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/server/settings/ClaudeSettingsPaths.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.server.settings 2 | 3 | import java.nio.file.Path 4 | import java.nio.file.Paths 5 | 6 | /** 7 | * Claude settings 配置文件路径工具。 8 | * 9 | * 当前支持三个层级(从低到高): 10 | * 1. 用户级: ~/.claude/settings.json 11 | * 2. 项目级共享: /.claude/settings.json 12 | * 3. 项目级本地: /.claude/settings.local.json 13 | */ 14 | object ClaudeSettingsPaths { 15 | 16 | fun userSettingsPath(): Path? { 17 | val home = System.getProperty("user.home") ?: return null 18 | return Paths.get(home, ".claude", "settings.json") 19 | } 20 | 21 | fun projectSettingsPath(projectPath: Path): Path { 22 | return projectPath.resolve(".claude").resolve("settings.json") 23 | } 24 | 25 | fun projectLocalSettingsPath(projectPath: Path): Path { 26 | return projectPath.resolve(".claude").resolve("settings.local.json") 27 | } 28 | } 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/server/rpc/ClientCaller.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.server.rpc 2 | 3 | import com.asakii.rpc.proto.AskUserQuestionRequest 4 | import com.asakii.rpc.proto.AskUserQuestionResponse 5 | import com.asakii.rpc.proto.RequestPermissionRequest 6 | import com.asakii.rpc.proto.RequestPermissionResponse 7 | 8 | /** 9 | * 客户端调用接口 - 用于服务器向前端发起 RPC 请求 10 | * 11 | * 这个接口允许后端(如 MCP Server)调用前端方法并等待响应。 12 | * 主要用于需要用户交互的场景,如 AskUserQuestion 工具。 13 | * 14 | * 使用 Protobuf 序列化进行类型化调用。 15 | */ 16 | interface ClientCaller { 17 | /** 18 | * 调用前端 AskUserQuestion(Protobuf 序列化) 19 | * 20 | * @param request AskUserQuestion 请求 21 | * @return AskUserQuestion 响应 22 | */ 23 | suspend fun callAskUserQuestion(request: AskUserQuestionRequest): AskUserQuestionResponse 24 | 25 | /** 26 | * 调用前端 RequestPermission(Protobuf 序列化) 27 | * 28 | * @param request RequestPermission 请求 29 | * @return RequestPermission 响应 30 | */ 31 | suspend fun callRequestPermission(request: RequestPermissionRequest): RequestPermissionResponse 32 | } 33 | -------------------------------------------------------------------------------- /codex-agent-sdk/src/main/kotlin/com/asakii/codex/agent/sdk/CodexOptions.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.codex.agent.sdk 2 | 3 | import java.nio.file.Path 4 | 5 | /** 6 | * 全局 Codex 客户端配置。 7 | * 8 | * Java 使用示例: 9 | * ```java 10 | * // 使用默认配置 11 | * CodexClientOptions options = new CodexClientOptions(); 12 | * 13 | * // 自定义配置 14 | * CodexClientOptions options = new CodexClientOptions( 15 | * null, // codexPathOverride 16 | * "https://api.example.com", // baseUrl 17 | * "your-api-key", // apiKey 18 | * Map.of("KEY", "VALUE") // env 19 | * ); 20 | * ``` 21 | */ 22 | data class CodexClientOptions @JvmOverloads constructor( 23 | /** Codex CLI 可执行文件路径覆盖 */ 24 | @JvmField val codexPathOverride: Path? = null, 25 | /** API 基础 URL */ 26 | @JvmField val baseUrl: String? = null, 27 | /** API 密钥 */ 28 | @JvmField val apiKey: String? = null, 29 | /** 30 | * 传递给 Codex CLI 进程的环境变量。 31 | * 如果提供,则不会继承当前进程的环境。 32 | */ 33 | @JvmField val env: Map? = null, 34 | ) 35 | 36 | 37 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin-compat-diff-243/com/asakii/plugin/compat/DiffEditorCompat.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.compat 2 | 3 | import com.intellij.diff.editor.DiffEditorViewerFileEditor 4 | import com.intellij.diff.impl.DiffRequestProcessor 5 | import com.intellij.diff.requests.DiffRequest 6 | import com.intellij.openapi.fileEditor.FileEditor 7 | 8 | /** 9 | * Diff Editor 兼容层 - 适用于 2024.3+ (243+) 10 | * 11 | * 使用 DiffEditorViewerFileEditor.editorViewer 获取 DiffRequestProcessor, 12 | * 再通过 getActiveRequest() 直接 API 调用获取 DiffRequest 13 | */ 14 | object DiffEditorCompat { 15 | 16 | /** 17 | * 从 FileEditor 获取 DiffRequest 18 | * 19 | * @param diffEditor Diff 编辑器 20 | * @return DiffRequest,如果无法获取则返回 null 21 | */ 22 | fun getActiveRequest(diffEditor: FileEditor): DiffRequest? { 23 | if (diffEditor is DiffEditorViewerFileEditor) { 24 | val viewer = diffEditor.editorViewer 25 | if (viewer is DiffRequestProcessor) { 26 | return viewer.activeRequest 27 | } 28 | } 29 | return null 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Claude Code Plus Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/adapters/IdeIntegration.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.adapters 2 | 3 | import com.asakii.plugin.types.ToolCallItem 4 | import com.asakii.plugin.types.LegacyToolCall 5 | import java.util.* 6 | 7 | /** 8 | * IDE 集成接口 9 | * 定义 IDE 相关的操作接口 10 | */ 11 | interface IdeIntegration { 12 | /** 13 | * 处理工具点击事件 14 | */ 15 | fun handleToolClick(toolCall: LegacyToolCall): Boolean 16 | 17 | /** 18 | * 打开文件 19 | */ 20 | fun openFile(filePath: String, line: Int? = null, column: Int? = null): Boolean 21 | 22 | /** 23 | * 显示文件差异 24 | */ 25 | fun showDiff(filePath: String, oldContent: String, newContent: String): Boolean 26 | 27 | /** 28 | * 显示通知 29 | */ 30 | fun showNotification(message: String, type: NotificationType) 31 | 32 | /** 33 | * 是否支持 34 | */ 35 | fun isSupported(): Boolean 36 | 37 | /** 38 | * 获取 IDE 语言设置 39 | */ 40 | fun getIdeLocale(): Locale 41 | } 42 | 43 | /** 44 | * 通知类型 45 | */ 46 | enum class NotificationType { 47 | INFO, 48 | WARNING, 49 | ERROR 50 | } 51 | 52 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin-compat-242/com/asakii/plugin/compat/BrowseButtonCompat.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.compat 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptor 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.ui.TextFieldWithBrowseButton 6 | 7 | /** 8 | * 文件浏览按钮兼容层 - 适用于 2024.2 ~ 2025.2 9 | * 10 | * 使用 4 参数的 addBrowseFolderListener API (title, description, project, descriptor) 11 | * 此 API 在 2025.3 中被标记为 deprecated,但在旧版本中是唯一可用的 API 12 | */ 13 | object BrowseButtonCompat { 14 | 15 | /** 16 | * 添加文件夹浏览监听器 17 | * 18 | * @param textField 带浏览按钮的文本框 19 | * @param title 对话框标题 20 | * @param description 对话框描述 21 | * @param project 项目(可选) 22 | * @param descriptor 文件选择器描述符 23 | */ 24 | fun addBrowseFolderListener( 25 | textField: TextFieldWithBrowseButton, 26 | title: String, 27 | description: String, 28 | project: Project?, 29 | descriptor: FileChooserDescriptor 30 | ) { 31 | @Suppress("DEPRECATION") 32 | textField.addBrowseFolderListener(title, description, project, descriptor) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/kotlin/com/asakii/claude/agent/sdk/transport/Transport.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk.transport 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.serialization.json.JsonElement 5 | 6 | /** 7 | * Abstract transport interface for communicating with Claude CLI. 8 | */ 9 | interface Transport { 10 | /** 11 | * Connect to the Claude CLI process. 12 | */ 13 | suspend fun connect() 14 | 15 | /** 16 | * Write data to the CLI stdin. 17 | */ 18 | suspend fun write(data: String) 19 | 20 | /** 21 | * Read messages from CLI stdout as a flow of JSON objects. 22 | */ 23 | fun readMessages(): Flow 24 | 25 | /** 26 | * Check if the transport is ready for communication. 27 | */ 28 | fun isReady(): Boolean 29 | 30 | /** 31 | * End the input stream to the CLI. 32 | */ 33 | suspend fun endInput() 34 | 35 | /** 36 | * Close the transport and cleanup resources. 37 | */ 38 | suspend fun close() 39 | 40 | /** 41 | * Check if the transport is connected. 42 | */ 43 | fun isConnected(): Boolean 44 | } -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/types/ToolDetails.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.types 2 | 3 | /** 4 | * 工具详情基类 5 | */ 6 | sealed interface ToolDetail 7 | 8 | /** 9 | * Edit 工具详情 10 | */ 11 | data class EditToolDetail( 12 | val filePath: String, 13 | val oldString: String, 14 | val newString: String, 15 | val replaceAll: Boolean = false 16 | ) : ToolDetail 17 | 18 | /** 19 | * MultiEdit 工具详情 20 | */ 21 | data class MultiEditToolDetail( 22 | val filePath: String, 23 | val edits: List 24 | ) : ToolDetail { 25 | data class EditOperation( 26 | val oldString: String, 27 | val newString: String, 28 | val replaceAll: Boolean = false 29 | ) 30 | } 31 | 32 | /** 33 | * Read 工具详情 34 | */ 35 | data class ReadToolDetail( 36 | val filePath: String, 37 | val offset: Int? = null, 38 | val limit: Int? = null 39 | ) : ToolDetail 40 | 41 | /** 42 | * Write 工具详情 43 | */ 44 | data class WriteToolDetail( 45 | val filePath: String 46 | ) : ToolDetail 47 | 48 | /** 49 | * 工具调用视图模型(简化版) 50 | */ 51 | data class ToolCallViewModel( 52 | val toolDetail: ToolDetail? 53 | ) 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /frontend/src/utils/contentBlockUtils.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ContentBlock, 3 | ToolUseContent, 4 | ToolResultContent, 5 | TextContent, 6 | ThinkingContent 7 | } from '@/types/message' 8 | 9 | export function isToolUseBlock(block: ContentBlock | unknown): block is ToolUseContent { 10 | return !!block && (block as ContentBlock).type === 'tool_use' && typeof (block as ToolUseContent).id === 'string' 11 | } 12 | 13 | export function isToolResultBlock(block: ContentBlock | unknown): block is ToolResultContent { 14 | return !!block && (block as ContentBlock).type === 'tool_result' && typeof (block as ToolResultContent).tool_use_id === 'string' 15 | } 16 | 17 | export function isTextBlock(block: ContentBlock | unknown): block is TextContent { 18 | return !!block && (block as ContentBlock).type === 'text' && typeof (block as TextContent).text === 'string' 19 | } 20 | 21 | export function isThinkingBlock(block: ContentBlock | unknown): block is ThinkingContent { 22 | return !!block && (block as ContentBlock).type === 'thinking' 23 | } 24 | 25 | export function extractToolUseBlocks(content: ContentBlock[]): ToolUseContent[] { 26 | return content.filter(isToolUseBlock) 27 | } 28 | -------------------------------------------------------------------------------- /edit_proto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """Script to edit proto file to add ModelInfoProto""" 4 | 5 | with open('ai-agent-proto/src/main/proto/ai_agent_rpc.proto', 'r', encoding='utf-8') as f: 6 | content = f.read() 7 | 8 | old_text = ' bool default_chrome_enabled = 12; // 默认启用 Chrome 扩展\n}\n\n// 思考级别配置' 9 | 10 | new_text = ''' bool default_chrome_enabled = 12; // 默认启用 Chrome 扩展 11 | repeated ModelInfoProto available_models = 13; // 所有可用模型(内置 + 自定义) 12 | } 13 | 14 | // 模型信息(内置模型和自定义模型通用) 15 | message ModelInfoProto { 16 | string id = 1; // 唯一标识:内置模型用枚举名(如 "OPUS_45"),自定义用 "custom_xxx" 17 | string display_name = 2; // 显示名称(如 "Opus 4.5", "My Custom Model") 18 | string model_id = 3; // 实际模型 ID(如 "claude-opus-4-5-20251101") 19 | bool is_built_in = 4; // 是否为内置模型 20 | } 21 | 22 | // 思考级别配置''' 23 | 24 | if old_text in content: 25 | content = content.replace(old_text, new_text) 26 | with open('ai-agent-proto/src/main/proto/ai_agent_rpc.proto', 'w', encoding='utf-8') as f: 27 | f.write(content) 28 | print('✅ Proto file updated successfully!') 29 | else: 30 | print('⚠️ Target text not found in proto file') 31 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/mcp/git/GetCommitMessageTool.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.mcp.git 2 | 3 | import com.intellij.openapi.project.Project 4 | 5 | /** 6 | * 获取 Commit Message 工具 7 | * 8 | * 读取 IDEA Commit 面板中的 commit message 输入框内容 9 | * 返回格式:Markdown 10 | */ 11 | class GetCommitMessageTool(private val project: Project) { 12 | 13 | suspend fun execute(arguments: Map): String { 14 | val accessor = CommitPanelAccessor.getInstance(project) 15 | val isOpen = accessor.isCommitPanelOpen() 16 | val message = accessor.getCommitMessage() ?: "" 17 | 18 | return buildString { 19 | appendLine("# Current Commit Message") 20 | appendLine() 21 | appendLine("- **Commit Panel Open**: ${if (isOpen) "Yes" else "No"}") 22 | appendLine() 23 | 24 | if (message.isNotBlank()) { 25 | appendLine("## Message Content") 26 | appendLine("```") 27 | appendLine(message) 28 | appendLine("```") 29 | } else { 30 | appendLine("*No commit message set.*") 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin-compat-253/com/asakii/plugin/compat/BrowseButtonCompat.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.compat 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptor 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.ui.TextFieldWithBrowseButton 6 | 7 | /** 8 | * 文件浏览按钮兼容层 - 适用于 2025.3+ 9 | * 10 | * 使用新的 2 参数 API,通过 FileChooserDescriptor 设置标题和描述 11 | */ 12 | object BrowseButtonCompat { 13 | 14 | /** 15 | * 添加文件夹浏览监听器 16 | * 17 | * @param textField 带浏览按钮的文本框 18 | * @param title 对话框标题 19 | * @param description 对话框描述 20 | * @param project 项目(可选) 21 | * @param descriptor 文件选择器描述符 22 | */ 23 | fun addBrowseFolderListener( 24 | textField: TextFieldWithBrowseButton, 25 | title: String, 26 | description: String, 27 | project: Project?, 28 | descriptor: FileChooserDescriptor 29 | ) { 30 | // 2025.3+ 使用新的 2 参数 API,通过 descriptor 设置标题和描述 31 | val configuredDescriptor = descriptor 32 | .withTitle(title) 33 | .withDescription(description) 34 | textField.addBrowseFolderListener(project, configuredDescriptor) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin-compat-243/com/asakii/plugin/compat/BrowseButtonCompat.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.compat 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptor 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.ui.TextFieldWithBrowseButton 6 | 7 | /** 8 | * 文件浏览按钮兼容层 - 适用于 2024.3+ 9 | * 10 | * 在这些版本中,使用新的 2 参数 API,通过 FileChooserDescriptor 设置标题和描述 11 | */ 12 | object BrowseButtonCompat { 13 | 14 | /** 15 | * 添加文件夹浏览监听器 16 | * 17 | * @param textField 带浏览按钮的文本框 18 | * @param title 对话框标题 19 | * @param description 对话框描述 20 | * @param project 项目(可选) 21 | * @param descriptor 文件选择器描述符 22 | */ 23 | fun addBrowseFolderListener( 24 | textField: TextFieldWithBrowseButton, 25 | title: String, 26 | description: String, 27 | project: Project?, 28 | descriptor: FileChooserDescriptor 29 | ) { 30 | // 2024.3+ 使用新的 2 参数 API,通过 descriptor 设置标题和描述 31 | val configuredDescriptor = descriptor 32 | .withTitle(title) 33 | .withDescription(description) 34 | textField.addBrowseFolderListener(project, configuredDescriptor) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.intellij.platform.gradle.extensions.intellijPlatform 2 | 3 | 4 | pluginManagement { 5 | repositories { 6 | google() 7 | gradlePluginPortal() 8 | mavenCentral() 9 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 10 | } 11 | } 12 | 13 | plugins { 14 | id("org.jetbrains.intellij.platform.settings") version "2.10.4" 15 | } 16 | 17 | dependencyResolutionManagement { 18 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 19 | repositories { 20 | mavenCentral() 21 | google() 22 | intellijPlatform { 23 | defaultRepositories() 24 | } 25 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 26 | maven("https://packages.jetbrains.team/maven/p/kmp/public") 27 | } 28 | 29 | 30 | } 31 | 32 | rootProject.name = "claude-code-plus" 33 | 34 | // 子模块配置 35 | include( 36 | "toolwindow", 37 | "jetbrains-plugin", 38 | "claude-agent-sdk", 39 | "codex-agent-sdk", 40 | "ai-agent-sdk", 41 | "ai-agent-rpc-api", 42 | "ai-agent-proto", // RSocket + Protobuf 通信模块 43 | "standalone-test", 44 | "ai-agent-server" 45 | ) 46 | -------------------------------------------------------------------------------- /frontend/src/components/chat/CompactingCard.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 58 | -------------------------------------------------------------------------------- /frontend/src/types/bridge.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 前后端通信协议类型定义 3 | */ 4 | 5 | // 前端请求格式 6 | export interface FrontendRequest { 7 | action: string 8 | data?: any 9 | } 10 | 11 | // 后端响应格式 12 | export interface FrontendResponse { 13 | success: boolean 14 | data?: any 15 | error?: string 16 | } 17 | 18 | // 后端推送事件格式 19 | export interface IdeEvent { 20 | type: string 21 | data?: any 22 | } 23 | 24 | // IDE 主题定义(与 themeService.ThemeColors 保持一致) 25 | export interface IdeTheme { 26 | background: string 27 | foreground: string 28 | borderColor: string 29 | panelBackground: string 30 | textFieldBackground: string 31 | selectionBackground: string 32 | selectionForeground: string 33 | linkColor: string 34 | errorColor: string 35 | warningColor: string 36 | successColor: string 37 | separatorColor: string 38 | hoverBackground: string 39 | accentColor: string 40 | infoBackground: string 41 | codeBackground: string 42 | secondaryForeground: string 43 | } 44 | 45 | // 全局类型扩展 46 | declare global { 47 | interface Window { 48 | ideaBridge: { 49 | query: (action: string, data?: any) => Promise 50 | } 51 | onIdeEvent: (event: IdeEvent) => void 52 | __bridgeReady: boolean 53 | } 54 | } 55 | 56 | export {} 57 | -------------------------------------------------------------------------------- /frontend/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Composables 导出索引 3 | * 4 | * Tab 自治架构的核心 Composables: 5 | * - useSessionTab: 核心入口,组合其他 composables 6 | * - useSessionTools: 工具调用管理 7 | * - useSessionStats: 统计管理 8 | * - useSessionPermissions: 权限管理 9 | * - useSessionMessages: 消息处理 10 | */ 11 | 12 | // 核心入口 13 | export { useSessionTab } from './useSessionTab' 14 | export type { SessionTabInstance, TabInfo, UIState, TabConnectOptions } from './useSessionTab' 15 | 16 | // 工具调用管理 17 | export { useSessionTools } from './useSessionTools' 18 | export type { SessionToolsInstance, ToolCallState } from './useSessionTools' 19 | 20 | // 统计管理 21 | export { useSessionStats } from './useSessionStats' 22 | export type { SessionStatsInstance, RequestTrackerInfo, CumulativeStats } from './useSessionStats' 23 | 24 | // 权限管理 25 | export { useSessionPermissions } from './useSessionPermissions' 26 | export type { SessionPermissionsInstance } from './useSessionPermissions' 27 | 28 | // 消息处理 29 | export { useSessionMessages } from './useSessionMessages' 30 | export type { SessionMessagesInstance } from './useSessionMessages' 31 | 32 | // 其他 composables 33 | export { useI18n } from './useI18n' 34 | export { useEnvironment } from './useEnvironment' 35 | export { useKeyboardShortcuts } from './useKeyboardShortcuts' 36 | -------------------------------------------------------------------------------- /frontend/src/components/chat/LocalCommandOutput.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 49 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/handlers/WriteToolHandler.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.handlers 2 | 3 | import com.asakii.plugin.services.IdeaPlatformService 4 | import com.asakii.plugin.types.WriteToolDetail 5 | import com.asakii.plugin.types.LegacyToolCall 6 | import com.intellij.openapi.diagnostic.Logger 7 | import com.intellij.openapi.project.Project 8 | 9 | /** 10 | * Write 工具点击处理器 11 | * 在 IDEA 编辑器中打开刚写入的文件 12 | * 13 | * 使用 IdeaPlatformService 统一服务,简化实现 14 | */ 15 | class WriteToolHandler : ToolClickHandler { 16 | 17 | companion object { 18 | private val logger = Logger.getInstance(WriteToolHandler::class.java) 19 | } 20 | 21 | override fun canHandle(toolCall: LegacyToolCall): Boolean { 22 | return false // 临时禁用 23 | } 24 | 25 | override fun handleToolClick( 26 | toolCall: LegacyToolCall, 27 | project: Project?, 28 | config: ToolClickConfig 29 | ): Boolean { 30 | if (project == null) { 31 | logger.info("WriteToolHandler: Project is null, 无法使用 IDE 集成") 32 | return false 33 | } 34 | 35 | if (config.alwaysExpand) { 36 | logger.info("WriteToolHandler: 配置为总是展开,跳过 IDE 集成") 37 | return false 38 | } 39 | 40 | // 临时简化处理 - 待重构后重新实现 41 | return false 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/services/ClaudeCodePlusBackgroundService.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.services 2 | 3 | import com.asakii.plugin.types.SessionState 4 | import com.asakii.plugin.types.SessionUpdate 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.flowOf 7 | 8 | /** 9 | * Claude Code Plus 后台服务 10 | * 11 | * 这是从 cli-wrapper 模块迁移到 claude-agent-sdk 的简化版本, 12 | * 包含了 jetbrains-plugin 模块所需的基本类型定义。 13 | */ 14 | object ClaudeCodePlusBackgroundService { 15 | 16 | /** 17 | * 获取会话状态 18 | */ 19 | fun getSessionState(sessionId: String): SessionState? { 20 | // 简化实现,返回空状态 21 | return null 22 | } 23 | 24 | /** 25 | * 观察会话更新 26 | */ 27 | fun observeSessionUpdates(sessionId: String): Flow { 28 | // 简化实现,返回空流 29 | return flowOf() 30 | } 31 | 32 | /** 33 | * 获取服务统计信息 34 | */ 35 | fun getServiceStats(): Map { 36 | return mapOf( 37 | "activeSessionsCount" to 0, 38 | "totalMessages" to 0, 39 | "status" to "ready" 40 | ) 41 | } 42 | 43 | /** 44 | * 观察项目更新 45 | */ 46 | fun observeProjectUpdates(projectPath: String): Flow> { 47 | // 简化实现,返回空映射流 48 | return flowOf(emptyMap()) 49 | } 50 | } -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/messages/ClaudeCodePlusBundle.properties: -------------------------------------------------------------------------------- 1 | # Claude Code Plus Plugin - Messages Bundle (English) 2 | 3 | # MCP Configuration 4 | mcp.settings.notice=Note: MCP servers configured here only apply to Claude Code Plus plugin sessions. \ 5 | They do not affect the standalone Claude Code CLI. 6 | mcp.settings.custom.warning=Proceed with caution and only connect to trusted servers. 7 | 8 | # Agents Configuration 9 | agents.settings.notice=Note: Custom agents configured here only apply to Claude Code Plus plugin sessions. \ 10 | They do not affect the standalone Claude Code CLI. 11 | 12 | # Dialog titles 13 | dialog.edit.builtin.mcp=Edit {0} 14 | dialog.new.mcp.server=New MCP Server 15 | dialog.edit.mcp.server=Edit MCP Server 16 | 17 | # Labels 18 | label.enable=Enable 19 | label.json.configuration=JSON configuration: 20 | label.appended.system.prompt=Appended System Prompt (optional): 21 | label.server.level=Server level: 22 | label.global=Global 23 | label.project=Project 24 | label.global.hint=Global: all projects 25 | label.project.hint=Project: current project only 26 | label.api.key=API Key (optional): 27 | label.reset.to.default=Reset to Default 28 | 29 | # Placeholders 30 | placeholder.json.stdio={"server-name": {"command": "...", "args": [...]}} 31 | placeholder.json.http=HTTP: {"name": {"type": "http", "url": "https://..."}} 32 | -------------------------------------------------------------------------------- /frontend/src/utils/operationQueue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 操作队列 - 确保所有操作严格顺序执行 3 | * 4 | * 用于防止并发操作导致的状态混乱,如快速连续发送消息、生成中切换配置等 5 | */ 6 | export class OperationQueue { 7 | private queue: Promise = Promise.resolve() 8 | private _isPending = false 9 | 10 | get isPending(): boolean { 11 | return this._isPending 12 | } 13 | 14 | /** 15 | * 将操作加入队列 16 | * @param operation 要执行的异步操作 17 | * @returns 操作结果的 Promise 18 | */ 19 | async enqueue(operation: () => Promise): Promise { 20 | let resolve: (value: T) => void 21 | let reject: (error: Error) => void 22 | 23 | const resultPromise = new Promise((res, rej) => { 24 | resolve = res 25 | reject = rej 26 | }) 27 | 28 | // 将新操作追加到队列 29 | this.queue = this.queue 30 | .then(async () => { 31 | this._isPending = true 32 | try { 33 | const result = await operation() 34 | resolve!(result) 35 | } catch (error) { 36 | reject!(error as Error) 37 | } finally { 38 | this._isPending = false 39 | } 40 | }) 41 | .catch(() => { 42 | // 确保队列不会因为错误而中断 43 | }) 44 | 45 | return resultPromise 46 | } 47 | 48 | /** 49 | * 清空队列(用于会话断开、中断操作等场景) 50 | */ 51 | clear(): void { 52 | this.queue = Promise.resolve() 53 | this._isPending = false 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /codex-agent-sdk/README.md: -------------------------------------------------------------------------------- 1 | # Codex Agent SDK (Kotlin) 2 | 3 | > Kotlin 版 Codex SDK,接口与 `external/openai-codex/sdk/typescript` 保持一致,便于 JVM 应用直接驱动 Codex CLI。 4 | 5 | ## 快速开始 6 | 7 | ```kotlin 8 | val client = CodexClient( 9 | CodexClientOptions( 10 | apiKey = System.getenv("CODEX_API_KEY"), 11 | ) 12 | ) 13 | 14 | val session = client.startThread( 15 | ThreadOptions( 16 | model = "claude-3.5-sonnet", 17 | sandboxMode = SandboxMode.WORKSPACE_WRITE, 18 | ) 19 | ) 20 | 21 | val result = session.run("请列出项目 README 的要点") 22 | println(result.finalResponse) 23 | ``` 24 | 25 | * `run`:执行一个完整回合,返回 Agent 响应、事件列表与 token 用量。 26 | * `runStreamed`:返回 `Flow`,可用于实时 UI。 27 | * `ThreadOptions`:设置模型、沙箱、附加目录、审批策略等。 28 | * `TurnOptions.outputSchema`:传入 JSON Schema,即可要求结构化输出。 29 | 30 | ## 架构 31 | 32 | | 组件 | 说明 | 33 | | --- | --- | 34 | | `CodexClient` | SDK 入口,负责创建/恢复线程 | 35 | | `CodexSession` | 线程上下文,封装 `run` 与 `runStreamed` | 36 | | `CodexExecProcess` | 子进程执行器,封装 `codex exec --experimental-json` | 37 | | `ThreadEvent` / `ThreadItem` | 对齐 TypeScript SDK 的事件 / 项目结构 | 38 | | `UserInput` | 支持文本与本地图片输入 | 39 | 40 | ## TODO 41 | 42 | - [ ] 更多测试覆盖(特别是 CLI 参数与错误处理) 43 | - [ ] 与 `claude-agent-sdk` 共享通用工具包 44 | - [ ] 扩展到 WebSocket / HTTP 模式的 Codex API 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /codex-agent-sdk/src/main/kotlin/com/asakii/codex/agent/sdk/OutputSchemaFile.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.codex.agent.sdk 2 | 3 | import java.io.IOException 4 | import java.nio.file.Files 5 | import java.nio.file.Path 6 | import java.nio.file.StandardOpenOption 7 | import kotlinx.serialization.json.Json 8 | import kotlinx.serialization.json.JsonObject 9 | 10 | internal data class OutputSchemaHandle( 11 | val schemaPath: Path?, 12 | val cleanup: suspend () -> Unit, 13 | ) 14 | 15 | internal suspend fun createOutputSchemaFile(schema: JsonObject?): OutputSchemaHandle { 16 | if (schema == null) { 17 | return OutputSchemaHandle(schemaPath = null) {} 18 | } 19 | 20 | val dir = Files.createTempDirectory("codex-output-schema") 21 | val schemaPath = dir.resolve("schema.json") 22 | val json = Json.encodeToString(JsonObject.serializer(), schema) 23 | 24 | try { 25 | Files.writeString(schemaPath, json, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING) 26 | } catch (error: IOException) { 27 | Files.deleteIfExists(schemaPath) 28 | Files.deleteIfExists(dir) 29 | throw error 30 | } 31 | 32 | return OutputSchemaHandle( 33 | schemaPath = schemaPath, 34 | cleanup = { 35 | runCatching { Files.deleteIfExists(schemaPath) } 36 | runCatching { Files.deleteIfExists(dir) } 37 | }, 38 | ) 39 | } 40 | 41 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/bridge/ResponseHelper.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.bridge 2 | 3 | import kotlinx.serialization.json.JsonElement 4 | import kotlinx.serialization.json.JsonPrimitive 5 | import kotlinx.serialization.json.JsonObject 6 | import kotlinx.serialization.encodeToString 7 | import kotlinx.serialization.json.Json 8 | 9 | /** 10 | * FrontendResponse 辅助工具 11 | * 提供便捷的成功/错误响应创建方法 12 | */ 13 | object ResponseHelper { 14 | @PublishedApi 15 | internal val json = Json { 16 | ignoreUnknownKeys = true 17 | isLenient = true 18 | encodeDefaults = true 19 | } 20 | 21 | /** 22 | * 创建成功响应(任意数据类型) 23 | */ 24 | inline fun success(data: T): FrontendResponse { 25 | // 将数据序列化为 JsonElement 26 | val jsonString = json.encodeToString(data) 27 | val jsonElement = json.parseToJsonElement(jsonString) 28 | 29 | return FrontendResponse( 30 | success = true, 31 | data = if (jsonElement is JsonObject) { 32 | jsonElement.toMap() 33 | } else { 34 | mapOf("value" to jsonElement) 35 | } 36 | ) 37 | } 38 | 39 | /** 40 | * 创建错误响应 41 | */ 42 | fun error(message: String): FrontendResponse { 43 | return FrontendResponse( 44 | success = false, 45 | error = message 46 | ) 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/resources/patches/README.md: -------------------------------------------------------------------------------- 1 | # CLI Patches 2 | 3 | This directory contains patch definitions for enhancing the Claude CLI. 4 | 5 | ## Patch File Format 6 | 7 | Each patch file is a JSON file with the following structure: 8 | 9 | ```json 10 | { 11 | "id": "unique_patch_id", 12 | "description": "Human readable description", 13 | "version": "2.0.69", 14 | "priority": 100, 15 | "enabled": true, 16 | "patches": [ 17 | { 18 | "id": "sub_patch_id", 19 | "description": "What this specific change does", 20 | "search": "exact string to find", 21 | "replace": "replacement string", 22 | "type": "replace|before|after", 23 | "required": true 24 | } 25 | ] 26 | } 27 | ``` 28 | 29 | 30 | 31 | 32 | - `replace`: Replace the search string with the replacement 33 | - `before`: Insert the replacement before the search string 34 | - `after`: Insert the replacement after the search string 35 | 36 | ## Execution Order 37 | 38 | Patches are applied in order of `priority` (lower numbers first). 39 | 40 | ## Creating a New Patch 41 | 42 | 1. Format the CLI code first: `./gradlew formatCli` 43 | 2. Find the exact string to search for 44 | 3. Create a patch JSON file in this directory 45 | 4. Test with: `./gradlew patchCli` 46 | 47 | ## Patch Application 48 | 49 | Patches are applied during build via `patchCli` task, producing: 50 | - `claude-cli-{version}.js` - Original CLI 51 | - `claude-cli-{version}-enhanced.js` - Patched CLI 52 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/mcp/tools/terminal/TerminalTypesTool.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.mcp.tools.terminal 2 | 3 | import mu.KotlinLogging 4 | 5 | private val logger = KotlinLogging.logger {} 6 | 7 | /** 8 | * TerminalTypes 工具 - 获取可用的 Shell 类型 9 | */ 10 | class TerminalTypesTool(private val sessionManager: TerminalSessionManager) { 11 | 12 | /** 13 | * 获取可用的 Shell 类型 14 | * 15 | * @param arguments 参数(无需参数) 16 | */ 17 | fun execute(arguments: Map): Map { 18 | logger.info { "Getting available shell types" } 19 | 20 | val types = sessionManager.getAvailableShellTypes() 21 | val platform = if (System.getProperty("os.name").lowercase().contains("windows")) { 22 | "windows" 23 | } else { 24 | "unix" 25 | } 26 | 27 | val defaultType = types.find { it.isDefault }?.name ?: types.firstOrNull()?.name ?: "bash" 28 | 29 | return mapOf( 30 | "success" to true, 31 | "platform" to platform, 32 | "types" to types.map { type -> 33 | buildMap { 34 | put("name", type.name) 35 | put("display_name", type.displayName) 36 | type.command?.let { put("command", it) } 37 | put("is_default", type.isDefault) 38 | } 39 | }, 40 | "default_type" to defaultType 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/services/GitBranchService.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.services 2 | 3 | import com.intellij.openapi.components.Service 4 | import com.intellij.openapi.project.Project 5 | 6 | /** 7 | * Git 分支服务接口 8 | * 9 | * 使用 JetBrains 官方推荐的可选依赖模式: 10 | * - 默认实现 (NoopGitBranchService): 当 Git4Idea 未安装时使用 11 | * - Git4Idea 实现 (GitBranchServiceImpl): 当 Git4Idea 已安装时使用,在 plugin-withGit.xml 中注册 12 | * 13 | * 这样可以避免使用反射,使用编译时类型安全的 API 14 | */ 15 | interface GitBranchService { 16 | 17 | /** 18 | * 获取当前分支名称 19 | * @return 分支名称,如果不可用则返回 null 20 | */ 21 | fun getCurrentBranchName(): String? 22 | 23 | /** 24 | * 获取所有本地分支名称 25 | * @return 分支名称列表 26 | */ 27 | fun getLocalBranches(): List 28 | 29 | /** 30 | * 检查 Git 是否可用 31 | * @return true 如果项目有 Git 仓库 32 | */ 33 | fun isGitAvailable(): Boolean 34 | 35 | companion object { 36 | @JvmStatic 37 | fun getInstance(project: Project): GitBranchService { 38 | return project.getService(GitBranchService::class.java) 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * 默认实现 - 当 Git4Idea 插件未安装时使用 45 | * 所有方法返回空/null 值 46 | */ 47 | @Service(Service.Level.PROJECT) 48 | class NoopGitBranchService : GitBranchService { 49 | 50 | override fun getCurrentBranchName(): String? = null 51 | 52 | override fun getLocalBranches(): List = emptyList() 53 | 54 | override fun isGitAvailable(): Boolean = false 55 | } 56 | -------------------------------------------------------------------------------- /demo/TodoDemoTest.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.demo 2 | 3 | import org.junit.jupiter.api.Test 4 | import org.junit.jupiter.api.Assertions.* 5 | import org.junit.jupiter.api.BeforeEach 6 | 7 | class SimpleCalculatorTest { 8 | 9 | private lateinit var calculator: SimpleCalculator 10 | 11 | @BeforeEach 12 | fun setup() { 13 | calculator = SimpleCalculator() 14 | } 15 | 16 | @Test 17 | fun `test addition`() { 18 | assertEquals(5.0, calculator.add(2.0, 3.0)) 19 | assertEquals(0.0, calculator.add(-1.0, 1.0)) 20 | } 21 | 22 | @Test 23 | fun `test subtraction`() { 24 | assertEquals(2.0, calculator.subtract(5.0, 3.0)) 25 | assertEquals(-5.0, calculator.subtract(0.0, 5.0)) 26 | } 27 | 28 | @Test 29 | fun `test multiplication`() { 30 | assertEquals(15.0, calculator.multiply(3.0, 5.0)) 31 | assertEquals(0.0, calculator.multiply(0.0, 100.0)) 32 | } 33 | 34 | @Test 35 | fun `test division`() { 36 | assertEquals(2.0, calculator.divide(10.0, 5.0)) 37 | assertEquals(0.5, calculator.divide(1.0, 2.0)) 38 | } 39 | 40 | @Test 41 | fun `test division by zero throws exception`() { 42 | assertThrows(IllegalArgumentException::class.java) { 43 | calculator.divide(10.0, 0.0) 44 | } 45 | } 46 | 47 | @Test 48 | fun `test power`() { 49 | assertEquals(8.0, calculator.power(2.0, 3.0)) 50 | assertEquals(1.0, calculator.power(5.0, 0.0)) 51 | } 52 | } -------------------------------------------------------------------------------- /frontend/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Vue Virtual Scroller 4 | declare module 'vue-virtual-scroller' { 5 | import type { DefineComponent, ComponentPublicInstance } from 'vue' 6 | 7 | interface DynamicScrollerInstance extends ComponentPublicInstance { 8 | scrollToBottom(): void 9 | forceUpdate(): void 10 | $el: HTMLElement 11 | } 12 | 13 | export const DynamicScroller: DefineComponent<{ 14 | items: unknown[] 15 | keyField?: string 16 | direction?: 'vertical' | 'horizontal' 17 | listClass?: string 18 | itemClass?: string 19 | minItemSize?: number 20 | buffer?: number 21 | }, DynamicScrollerInstance> 22 | 23 | export const DynamicScrollerItem: DefineComponent<{ 24 | item: unknown 25 | active?: boolean 26 | sizeDependencies?: unknown[] 27 | watchData?: boolean 28 | }> 29 | 30 | export const RecycleScroller: DefineComponent<{ 31 | items: unknown[] 32 | keyField?: string 33 | direction?: 'vertical' | 'horizontal' 34 | itemSize?: number | null 35 | minItemSize?: number 36 | sizeField?: string 37 | typeField?: string 38 | buffer?: number 39 | pageMode?: boolean 40 | prerender?: number 41 | emitUpdate?: boolean 42 | listClass?: string 43 | itemClass?: string 44 | itemTag?: string 45 | listTag?: string 46 | }> 47 | } 48 | 49 | // Window extensions 50 | interface Window { 51 | __serverUrl?: string 52 | __pageUrl?: string 53 | __projectPath?: string 54 | } 55 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | root: true, 4 | env: { 5 | browser: true, 6 | es2021: true, 7 | node: true 8 | }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | 'plugin:vue/vue3-recommended', 13 | '@vue/typescript/recommended' 14 | ], 15 | parser: 'vue-eslint-parser', 16 | parserOptions: { 17 | ecmaVersion: 'latest', 18 | parser: '@typescript-eslint/parser', 19 | sourceType: 'module' 20 | }, 21 | plugins: ['@typescript-eslint', 'vue'], 22 | rules: { 23 | // Vue 规则 24 | 'vue/multi-word-component-names': 'off', // 允许单词组件名 25 | 'vue/no-v-html': 'off', // Markdown 渲染必须用 v-html 26 | 'vue/require-default-prop': 'off', // 不强制默认值 27 | 'vue/no-setup-props-destructure': 'off', // 允许 props 解构 28 | 'vue/one-component-per-file': 'off', // 允许子组件在同一文件 29 | 'vue/no-template-shadow': 'off', // 允许合理的变量名重用 30 | 31 | // TypeScript 规则 32 | '@typescript-eslint/no-explicit-any': 'off', // Bridge 接口需要 any 33 | '@typescript-eslint/no-unused-vars': ['warn', { 34 | argsIgnorePattern: '^_', 35 | varsIgnorePattern: '^_', 36 | destructuredArrayIgnorePattern: '^_' 37 | }], 38 | '@typescript-eslint/no-non-null-assertion': 'off', // 允许 ! 断言 39 | 40 | // 通用规则 41 | 'no-console': 'off', // 开发时允许 console 42 | 'no-debugger': 'warn', // debugger 警告 43 | 'prefer-const': 'warn', 44 | 'no-unused-vars': 'off' // 使用 TS 版本的规则 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/composables/useContextMenu.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 右键菜单相关的 composable 3 | * 处理发送按钮右键菜单等 4 | */ 5 | import { ref } from 'vue' 6 | 7 | export interface UseContextMenuOptions { 8 | /** 发送回调 */ 9 | onSend?: () => void 10 | /** 强制发送回调 */ 11 | onForceSend?: () => void 12 | } 13 | 14 | export function useContextMenu(options: UseContextMenuOptions = {}) { 15 | // 发送按钮右键菜单状态 16 | const showSendContextMenu = ref(false) 17 | const sendContextMenuPosition = ref({ x: 0, y: 0 }) 18 | 19 | /** 20 | * 处理发送按钮右键菜单 21 | */ 22 | function handleSendButtonContextMenu(event: MouseEvent) { 23 | event.preventDefault() 24 | showSendContextMenu.value = true 25 | sendContextMenuPosition.value = { 26 | x: event.clientX, 27 | y: event.clientY 28 | } 29 | } 30 | 31 | /** 32 | * 从右键菜单发送 33 | */ 34 | function handleSendFromContextMenu() { 35 | showSendContextMenu.value = false 36 | options.onSend?.() 37 | } 38 | 39 | /** 40 | * 从右键菜单强制发送 41 | */ 42 | function handleForceSendFromContextMenu() { 43 | showSendContextMenu.value = false 44 | options.onForceSend?.() 45 | } 46 | 47 | /** 48 | * 关闭发送按钮右键菜单 49 | */ 50 | function closeSendContextMenu() { 51 | showSendContextMenu.value = false 52 | } 53 | 54 | return { 55 | // 状态 56 | showSendContextMenu, 57 | sendContextMenuPosition, 58 | // 方法 59 | handleSendButtonContextMenu, 60 | handleSendFromContextMenu, 61 | handleForceSendFromContextMenu, 62 | closeSendContextMenu 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /frontend/src/components/chat/ErrorResultDisplay.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 67 | -------------------------------------------------------------------------------- /ai-agent-sdk/src/main/kotlin/com/asakii/ai/agent/sdk/capabilities/DefaultCapabilities.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.ai.agent.sdk.capabilities 2 | 3 | /** 4 | * Claude SDK 的能力声明 5 | * 6 | * Claude 支持完整的功能集,包括: 7 | * - 动态切换模型 8 | * - 权限模式切换 9 | * - 富媒体输入(图片) 10 | * - 思考功能 11 | * - 会话恢复 12 | */ 13 | object ClaudeCapabilities : AgentCapabilities { 14 | override val canInterrupt = true 15 | override val canSwitchModel = true 16 | override val canSwitchPermissionMode = true 17 | override val supportedPermissionModes = listOf( 18 | AiPermissionMode.DEFAULT, 19 | AiPermissionMode.ACCEPT_EDITS, 20 | AiPermissionMode.BYPASS_PERMISSIONS, 21 | AiPermissionMode.PLAN 22 | ) 23 | override val canSkipPermissions = true 24 | override val canSendRichContent = true 25 | override val canThink = true 26 | override val canResumeSession = true 27 | override val canRunInBackground = true 28 | } 29 | 30 | /** 31 | * Codex SDK 的能力声明 32 | * 33 | * Codex 功能相对有限: 34 | * - 不支持动态切换模型 35 | * - 不支持权限模式 36 | * - 只支持纯文本输入 37 | * - 不支持思考功能 38 | */ 39 | object CodexCapabilities : AgentCapabilities { 40 | override val canInterrupt = true 41 | override val canSwitchModel = false 42 | override val canSwitchPermissionMode = false 43 | override val supportedPermissionModes = emptyList() 44 | override val canSkipPermissions = false 45 | override val canSendRichContent = false 46 | override val canThink = false 47 | override val canResumeSession = true 48 | override val canRunInBackground = false 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/utils/serverUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务器 URL 解析工具 3 | * 4 | * - 浏览器开发模式(Vite):后端固定跑在 http://localhost:8765 5 | * - IDE 插件模式:前端由 Ktor 随机端口服务,使用相同 origin 6 | */ 7 | 8 | const DEFAULT_HTTP_URL = 'http://localhost:8765' 9 | const DEFAULT_WS_URL = 'ws://localhost:8765' 10 | 11 | /** 12 | * 获取 HTTP 基础 URL(用于 API 调用) 13 | */ 14 | export function resolveServerHttpUrl(): string { 15 | if (typeof window === 'undefined') { 16 | return DEFAULT_HTTP_URL 17 | } 18 | 19 | const anyWindow = window as any 20 | 21 | // IDEA 插件模式:使用注入的 __serverUrl 22 | if (anyWindow.__serverUrl) { 23 | return anyWindow.__serverUrl as string 24 | } 25 | 26 | // Vite 开发模式:前后端分离,后端固定端口 8765 27 | if (import.meta.env.DEV) { 28 | return DEFAULT_HTTP_URL 29 | } 30 | 31 | // 生产部署兜底:同源 32 | return window.location.origin 33 | } 34 | 35 | /** 36 | * 获取 WebSocket URL 37 | */ 38 | export function resolveServerWsUrl(): string { 39 | if (typeof window === 'undefined') { 40 | return DEFAULT_WS_URL 41 | } 42 | 43 | const anyWindow = window as any 44 | 45 | // IDEA 插件模式 46 | if (anyWindow.__serverUrl) { 47 | const url = new URL(anyWindow.__serverUrl as string) 48 | const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:' 49 | return `${wsProtocol}//${url.host}` 50 | } 51 | 52 | // Vite 开发模式:直接连 8765 53 | if (import.meta.env.DEV) { 54 | return DEFAULT_WS_URL 55 | } 56 | 57 | // 生产同源 58 | const { protocol, host } = window.location 59 | const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:' 60 | return `${wsProtocol}//${host}` 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/mcp/tools/terminal/TerminalInterruptTool.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.mcp.tools.terminal 2 | 3 | import mu.KotlinLogging 4 | 5 | private val logger = KotlinLogging.logger {} 6 | 7 | /** 8 | * TerminalInterrupt 工具 - 中断正在执行的命令(发送 Ctrl+C) 9 | */ 10 | class TerminalInterruptTool(private val sessionManager: TerminalSessionManager) { 11 | 12 | /** 13 | * 中断正在执行的命令 14 | * 15 | * @param arguments 参数: 16 | * - session_id: String - 会话 ID(必需) 17 | */ 18 | fun execute(arguments: Map): Map { 19 | val sessionId = arguments["session_id"] as? String 20 | ?: return mapOf( 21 | "success" to false, 22 | "error" to "Missing required parameter: session_id" 23 | ) 24 | 25 | logger.info { "Interrupting command in session: $sessionId" } 26 | 27 | val result = sessionManager.interruptCommand(sessionId) 28 | 29 | return if (result.success) { 30 | buildMap { 31 | put("success", true) 32 | put("session_id", result.sessionId) 33 | result.wasRunning?.let { put("was_running", it) } 34 | result.isStillRunning?.let { put("is_still_running", it) } 35 | result.message?.let { put("message", it) } 36 | } 37 | } else { 38 | mapOf( 39 | "success" to false, 40 | "session_id" to result.sessionId, 41 | "error" to (result.error ?: "Unknown error") 42 | ) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/kotlin/com/asakii/claude/agent/sdk/callback/ToolCallback.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk.callback 2 | 3 | import kotlinx.serialization.json.JsonElement 4 | 5 | /** 6 | * 工具回调接口 - 用于自定义工具的处理逻辑 7 | * 8 | * 当 Claude CLI 调用某个工具时,如果该工具已注册回调, 9 | * SDK 会调用回调而不是让 CLI 自动执行。 10 | * 11 | * 使用场景: 12 | * - AskUserQuestion: 需要前端交互,等待用户选择 13 | * - ExitPlanMode: 需要用户确认计划 14 | * - 其他需要自定义处理的工具 15 | * 16 | * 示例: 17 | * ```kotlin 18 | * class AskUserQuestionCallback : ToolCallback { 19 | * override val toolName = "AskUserQuestion" 20 | * 21 | * override suspend fun execute(toolId: String, input: JsonElement): ToolCallbackResult { 22 | * // 调用前端显示问题,等待用户选择 23 | * val answers = askFrontend(input) 24 | * return ToolCallbackResult( 25 | * content = "User answered: $answers", 26 | * isError = false 27 | * ) 28 | * } 29 | * } 30 | * ``` 31 | */ 32 | interface ToolCallback { 33 | /** 34 | * 工具名称,必须与 Claude CLI 中的工具名称完全匹配 35 | * 例如:"AskUserQuestion", "ExitPlanMode", "Read", "Write" 36 | */ 37 | val toolName: String 38 | 39 | /** 40 | * 执行工具回调 41 | * 42 | * @param toolId 工具调用 ID(如 "toolu_xxx"),用于关联 tool_result 43 | * @param input 工具输入参数(JSON 格式) 44 | * @return 工具执行结果 45 | */ 46 | suspend fun execute(toolId: String, input: JsonElement): ToolCallbackResult 47 | } 48 | 49 | /** 50 | * 工具回调执行结果 51 | * 52 | * @param content 返回给 Claude 的内容,将作为 tool_result 的 content 53 | * @param isError 是否为错误结果 54 | */ 55 | data class ToolCallbackResult( 56 | val content: String, 57 | val isError: Boolean = false 58 | ) 59 | -------------------------------------------------------------------------------- /frontend/src/types/sessionGroup.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 会话分组和标签类型定义 3 | */ 4 | 5 | export interface SessionGroup { 6 | id: string 7 | name: string 8 | description?: string 9 | color: string 10 | icon?: string 11 | parentId?: string // 支持嵌套分组 12 | order: number 13 | isCollapsed: boolean 14 | } 15 | 16 | export interface SessionTag { 17 | id: string 18 | name: string 19 | color: string 20 | description?: string 21 | } 22 | 23 | export enum SessionStatus { 24 | ACTIVE = 'ACTIVE', 25 | INTERRUPTED = 'INTERRUPTED', 26 | COMPLETED = 'COMPLETED', 27 | ARCHIVED = 'ARCHIVED', 28 | LOADING = 'LOADING', 29 | ERROR = 'ERROR' 30 | } 31 | 32 | // 扩展 Session 类型以包含分组和标签 33 | export interface SessionWithGrouping { 34 | id: string 35 | name: string 36 | timestamp: number 37 | groupId?: string 38 | tags: SessionTag[] 39 | status: SessionStatus 40 | summary?: string 41 | } 42 | 43 | // 预定义的颜色 44 | export const GROUP_COLORS = [ 45 | '#1976D2', // 蓝色 46 | '#388E3C', // 绿色 47 | '#F57C00', // 橙色 48 | '#7B1FA2', // 紫色 49 | '#C62828', // 红色 50 | '#0097A7', // 青色 51 | '#5D4037', // 棕色 52 | '#455A64' // 灰蓝色 53 | ] 54 | 55 | export const TAG_COLORS = [ 56 | '#2196F3', // 亮蓝色 57 | '#4CAF50', // 亮绿色 58 | '#FF9800', // 亮橙色 59 | '#9C27B0', // 亮紫色 60 | '#F44336', // 亮红色 61 | '#00BCD4', // 亮青色 62 | '#795548', // 亮棕色 63 | '#607D8B' // 亮灰蓝色 64 | ] 65 | 66 | // 预定义的图标 67 | export const GROUP_ICONS = [ 68 | '📁', '📂', '📊', '📈', '📉', '📋', '📌', '📍', 69 | '🎯', '🎨', '🎭', '🎪', '🎬', '🎮', '🎲', '🎰', 70 | '💼', '💻', '💡', '💬', '💭', '💾', '💿', '📀', 71 | '🔧', '🔨', '🔩', '🔪', '🔫', '🔬', '🔭', '🔮' 72 | ] 73 | 74 | -------------------------------------------------------------------------------- /frontend/src/config/modelConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * AI 模型配置 3 | * 定义每个模型的上下文长度和其他属性 4 | */ 5 | 6 | import { AiModel } from '@/types/enhancedMessage' 7 | 8 | export interface ModelConfig { 9 | id: AiModel 10 | name: string 11 | contextLength: number 12 | description?: string 13 | } 14 | 15 | /** 16 | * 模型配置映射 17 | * 基于 Claude API 的实际上下文窗口大小 18 | */ 19 | export const MODEL_CONFIGS: Record = { 20 | [AiModel.DEFAULT]: { 21 | id: AiModel.DEFAULT, 22 | name: 'Claude Sonnet 4.5', 23 | contextLength: 200000, 24 | description: '默认模型 - Claude 3.5 Sonnet (2024-10-22)' 25 | }, 26 | [AiModel.OPUS]: { 27 | id: AiModel.OPUS, 28 | name: 'Claude Opus', 29 | contextLength: 200000, 30 | description: 'Claude 3 Opus - 最强大的模型' 31 | }, 32 | [AiModel.SONNET]: { 33 | id: AiModel.SONNET, 34 | name: 'Claude Sonnet', 35 | contextLength: 200000, 36 | description: 'Claude 3.5 Sonnet - 平衡性能和成本' 37 | }, 38 | [AiModel.OPUS_PLAN]: { 39 | id: AiModel.OPUS_PLAN, 40 | name: 'Claude Opus (Plan Mode)', 41 | contextLength: 200000, 42 | description: 'Claude 3 Opus - 计划模式' 43 | } 44 | } 45 | 46 | /** 47 | * 获取模型配置 48 | * @throws {Error} 如果模型不存在 49 | */ 50 | export function getModelConfig(model: AiModel): ModelConfig { 51 | const config = MODEL_CONFIGS[model] 52 | if (!config) { 53 | throw new Error(`Unknown AI model: ${model}. Available models: ${Object.keys(MODEL_CONFIGS).join(', ')}`) 54 | } 55 | return config 56 | } 57 | 58 | /** 59 | * 获取模型的上下文长度 60 | * @throws {Error} 如果模型不存在 61 | */ 62 | export function getModelContextLength(model: AiModel | string): number { 63 | return MODEL_CONFIGS[model as AiModel]?.contextLength ?? 200000 64 | } 65 | 66 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/kotlin/com/asakii/claude/agent/sdk/builders/McpServerBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk.builders 2 | 3 | import com.asakii.claude.agent.sdk.mcp.* 4 | import kotlinx.serialization.json.* 5 | 6 | /** 7 | * 快捷工具函数 - 创建简单的单工具服务器 8 | * 9 | * 这是为不想使用注解的用户提供的手动注册方式。 10 | * 大多数情况下,推荐使用基于注解的 McpServerBase 实现方式。 11 | */ 12 | fun simpleTool( 13 | name: String, 14 | description: String = "", 15 | handler: suspend (Map) -> Any 16 | ): McpServer = object : McpServer { 17 | override val name: String = name 18 | override val version: String = "1.0.0" 19 | override val description: String = description.ifEmpty { "简单工具: $name" } 20 | 21 | private val tool = ToolHandler( 22 | name = name, 23 | description = this.description, 24 | parameterSchema = null, 25 | handler = handler 26 | ) 27 | 28 | override suspend fun listTools(): List { 29 | return listOf(tool.toDefinition()) 30 | } 31 | 32 | override suspend fun callTool(toolName: String, arguments: Map): ToolResult { 33 | if (toolName != name) { 34 | return ToolResult.error("工具 '$toolName' 未找到") 35 | } 36 | 37 | return try { 38 | val result = tool.handler(arguments) 39 | when (result) { 40 | is ToolResult -> result 41 | Unit -> ToolResult.success("操作完成") 42 | is String -> ToolResult.success(result) // 显式匹配 String 以调用正确的重载 43 | else -> ToolResult.success(result) 44 | } 45 | } catch (e: Exception) { 46 | ToolResult.error("工具执行失败: ${e.message}") 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/handlers/ToolClickHandler.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.handlers 2 | 3 | import com.asakii.plugin.types.LegacyToolCall 4 | import com.intellij.openapi.project.Project 5 | 6 | /** 7 | * 工具点击处理器接口 8 | * 用于处理不同工具的点击事件,提供与 IDEA 平台的深度集成 9 | */ 10 | interface ToolClickHandler { 11 | 12 | /** 13 | * 处理工具点击事件 14 | * 15 | * @param toolCall 工具调用信息 16 | * @param project IntelliJ 项目实例,可能为 null(桌面应用模式) 17 | * @param config 点击处理配置 18 | * @return true 表示已处理,false 表示使用默认展开行为 19 | */ 20 | fun handleToolClick( 21 | toolCall: LegacyToolCall, 22 | project: Project?, 23 | config: ToolClickConfig = ToolClickConfig() 24 | ): Boolean 25 | 26 | /** 27 | * 检查是否支持处理指定的工具 28 | */ 29 | fun canHandle(toolCall: LegacyToolCall): Boolean 30 | } 31 | 32 | /** 33 | * 工具点击配置 34 | */ 35 | data class ToolClickConfig( 36 | /** 优先使用 IDE 集成(默认 true) */ 37 | val preferIdeIntegration: Boolean = true, 38 | 39 | /** 总是展开(覆盖 IDE 集成,用于调试或用户偏好) */ 40 | val alwaysExpand: Boolean = false, 41 | 42 | /** IDE 集成失败时的回退行为 */ 43 | val fallbackBehavior: FallbackBehavior = FallbackBehavior.EXPAND, 44 | 45 | /** 显示操作成功的通知 */ 46 | val showNotifications: Boolean = true 47 | ) 48 | 49 | /** 50 | * 回退行为枚举 51 | */ 52 | enum class FallbackBehavior { 53 | /** 展开显示工具详情 */ 54 | EXPAND, 55 | 56 | /** 静默失败(不执行任何操作) */ 57 | SILENT, 58 | 59 | /** 显示错误消息 */ 60 | SHOW_ERROR 61 | } 62 | 63 | /** 64 | * 展开行为配置 65 | */ 66 | enum class ExpandBehavior { 67 | /** 从不展开 */ 68 | NEVER, 69 | 70 | /** 总是展开 */ 71 | ALWAYS, 72 | 73 | /** IDE 集成失败时展开 */ 74 | IDE_FALLBACK 75 | } -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/mcp/tools/terminal/TerminalRenameTool.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.mcp.tools.terminal 2 | 3 | import mu.KotlinLogging 4 | 5 | private val logger = KotlinLogging.logger {} 6 | 7 | /** 8 | * TerminalRename 工具 - 重命名终端会话 9 | */ 10 | class TerminalRenameTool(private val sessionManager: TerminalSessionManager) { 11 | 12 | /** 13 | * 重命名终端会话 14 | * 15 | * @param arguments 参数: 16 | * - session_id: String - 会话 ID(必需) 17 | * - new_name: String - 新名称(必需) 18 | */ 19 | fun execute(arguments: Map): Map { 20 | val sessionId = arguments["session_id"] as? String 21 | ?: return mapOf( 22 | "success" to false, 23 | "error" to "Missing required parameter: session_id" 24 | ) 25 | 26 | val newName = arguments["new_name"] as? String 27 | ?: return mapOf( 28 | "success" to false, 29 | "error" to "Missing required parameter: new_name" 30 | ) 31 | 32 | logger.info { "Renaming terminal session $sessionId to: $newName" } 33 | 34 | val success = sessionManager.renameSession(sessionId, newName) 35 | 36 | return if (success) { 37 | mapOf( 38 | "success" to true, 39 | "session_id" to sessionId, 40 | "new_name" to newName, 41 | "message" to "Session renamed successfully" 42 | ) 43 | } else { 44 | mapOf( 45 | "success" to false, 46 | "session_id" to sessionId, 47 | "error" to "Failed to rename session or session not found" 48 | ) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/components/chat/ContextUsageIndicator.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 55 | 56 | 72 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/theme/IdeaThemeAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.theme 2 | 3 | import com.intellij.ide.ui.LafManagerListener 4 | import com.intellij.openapi.application.ApplicationManager 5 | import com.intellij.openapi.diagnostic.Logger 6 | import com.intellij.ui.JBColor 7 | import com.intellij.util.ui.UIUtil 8 | import javax.swing.UIManager 9 | 10 | /** 11 | * IDEA 主题适配器 12 | * 负责监听 IDEA 主题变化并通知前端 13 | * 14 | * 设计原则:不判断亮暗,直接使用 IDE 颜色值 15 | */ 16 | class IdeaThemeAdapter { 17 | 18 | companion object { 19 | private val logger = Logger.getInstance(IdeaThemeAdapter::class.java) 20 | 21 | /** 22 | * 获取当前 IDEA 主题名称 23 | */ 24 | fun getCurrentThemeName(): String { 25 | return try { 26 | UIManager.getLookAndFeel().name 27 | } catch (e: Exception) { 28 | "Unknown" 29 | } 30 | } 31 | 32 | /** 33 | * 注册主题变化监听器 34 | * 主题变化时触发回调,回调参数为主题名称(而非亮暗判断) 35 | */ 36 | fun registerThemeChangeListener(onChange: (Boolean) -> Unit) { 37 | try { 38 | val connection = ApplicationManager.getApplication().messageBus.connect() 39 | 40 | connection.subscribe(LafManagerListener.TOPIC, LafManagerListener { 41 | val themeName = getCurrentThemeName() 42 | logger.info("IDEA 主题已更改: $themeName") 43 | // 回调参数保持兼容,但调用方应使用完整颜色值而非亮暗判断 44 | onChange(true) // 仅触发刷新,不传递亮暗信息 45 | }) 46 | 47 | logger.info("已注册 IDEA 主题变化监听器") 48 | } catch (e: Exception) { 49 | logger.error("注册主题监听器失败", e) 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /codex-agent-sdk/src/main/kotlin/com/asakii/codex/agent/sdk/ThreadOptions.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.codex.agent.sdk 2 | 3 | import kotlinx.coroutines.Job 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.json.JsonObject 6 | 7 | enum class ApprovalMode(val wireValue: String) { 8 | NEVER("never"), 9 | @SerialName("on-request") 10 | ON_REQUEST("on-request"), 11 | @SerialName("on-failure") 12 | ON_FAILURE("on-failure"), 13 | @SerialName("untrusted") 14 | UNTRUSTED("untrusted"), 15 | } 16 | 17 | enum class SandboxMode(val wireValue: String) { 18 | @SerialName("read-only") 19 | READ_ONLY("read-only"), 20 | @SerialName("workspace-write") 21 | WORKSPACE_WRITE("workspace-write"), 22 | @SerialName("danger-full-access") 23 | DANGER_FULL_ACCESS("danger-full-access"), 24 | } 25 | 26 | enum class ModelReasoningEffort(val wireValue: String) { 27 | MINIMAL("minimal"), 28 | LOW("low"), 29 | MEDIUM("medium"), 30 | HIGH("high"), 31 | } 32 | 33 | data class ThreadOptions @JvmOverloads constructor( 34 | val model: String? = null, 35 | val sandboxMode: SandboxMode? = null, 36 | val workingDirectory: String? = null, 37 | val skipGitRepoCheck: Boolean = false, 38 | val modelReasoningEffort: ModelReasoningEffort? = null, 39 | val networkAccessEnabled: Boolean? = null, 40 | val webSearchEnabled: Boolean? = null, 41 | val approvalPolicy: ApprovalMode? = null, 42 | val additionalDirectories: List = emptyList(), 43 | ) 44 | 45 | data class TurnOptions @JvmOverloads constructor( 46 | /** 47 | * 预期输出的 JSON Schema。 48 | */ 49 | val outputSchema: JsonObject? = null, 50 | /** 51 | * 可选的取消信号。取消时会终止底层 CLI 进程。 52 | */ 53 | val cancellation: Job? = null, 54 | ) 55 | 56 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/test/kotlin/com/asakii/claude/agent/sdk/InvalidModelSwitchTest.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk 2 | 3 | 4 | import com.asakii.claude.agent.sdk.types.ClaudeAgentOptions 5 | import kotlinx.coroutines.runBlocking 6 | import org.junit.jupiter.api.AfterEach 7 | import org.junit.jupiter.api.BeforeEach 8 | import org.junit.jupiter.api.Test 9 | 10 | class InvalidModelSwitchTest { 11 | 12 | private lateinit var client: ClaudeCodeSdkClient 13 | 14 | @BeforeEach 15 | fun setUp() = runBlocking { 16 | client = ClaudeCodeSdkClient( 17 | ClaudeAgentOptions(model = "claude-sonnet-4-20250514", maxTurns = 1) 18 | ) 19 | client.connect() 20 | } 21 | 22 | @AfterEach 23 | fun tearDown() = runBlocking { 24 | if (this@InvalidModelSwitchTest::client.isInitialized) { 25 | client.disconnect() 26 | } 27 | } 28 | 29 | @Test 30 | fun testSetModelWithInvalidId() = runBlocking { 31 | println("尝试 setModel(\"aaa\")") 32 | try { 33 | client.setModel("aaa") 34 | println("setModel 调用完成(未抛异常)") 35 | println("向 CLI 询问当前模型以验证响应") 36 | client.query("现在你使用的模型是什么?只回答模型 ID。") 37 | client.receiveResponse().collect { message -> 38 | if (message is com.asakii.claude.agent.sdk.types.AssistantMessage) { 39 | println("CLI 实际返回模型: ${message.model}") 40 | } 41 | if (message is com.asakii.claude.agent.sdk.types.ResultMessage) { 42 | return@collect 43 | } 44 | } 45 | } catch (e: Exception) { 46 | println("setModel 抛出异常: ${e::class.simpleName} - ${e.message}") 47 | e.printStackTrace() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/messages/ClaudeCodePlusBundle_zh_CN.properties: -------------------------------------------------------------------------------- 1 | # Claude Code Plus Plugin - Messages Bundle (Simplified Chinese) 2 | 3 | # MCP Configuration 4 | mcp.settings.notice=\u6CE8\u610F\uFF1A\u6B64\u5904\u914D\u7F6E\u7684 MCP \u670D\u52A1\u5668\u4EC5\u5BF9 Claude Code Plus \u63D2\u4EF6\u4F1A\u8BDD\u751F\u6548\uFF0C\ 5 | \u4E0D\u4F1A\u5F71\u54CD\u72EC\u7ACB\u7684 Claude Code \u547D\u4EE4\u884C\u5DE5\u5177\u3002 6 | mcp.settings.custom.warning=\u8BF7\u8C28\u614E\u64CD\u4F5C\uFF0C\u4EC5\u8FDE\u63A5\u53EF\u4FE1\u4EFB\u7684\u670D\u52A1\u5668\u3002 7 | 8 | # Agents Configuration 9 | agents.settings.notice=\u6CE8\u610F\uFF1A\u6B64\u5904\u914D\u7F6E\u7684\u81EA\u5B9A\u4E49\u4EE3\u7406\u4EC5\u5BF9 Claude Code Plus \u63D2\u4EF6\u4F1A\u8BDD\u751F\u6548\uFF0C\ 10 | \u4E0D\u4F1A\u5F71\u54CD\u72EC\u7ACB\u7684 Claude Code \u547D\u4EE4\u884C\u5DE5\u5177\u3002 11 | 12 | # Dialog titles 13 | dialog.edit.builtin.mcp=\u7F16\u8F91 {0} 14 | dialog.new.mcp.server=\u65B0\u5EFA MCP \u670D\u52A1\u5668 15 | dialog.edit.mcp.server=\u7F16\u8F91 MCP \u670D\u52A1\u5668 16 | 17 | # Labels 18 | label.enable=\u542F\u7528 19 | label.json.configuration=JSON \u914D\u7F6E\uFF1A 20 | label.appended.system.prompt=\u8FFD\u52A0\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF08\u53EF\u9009\uFF09\uFF1A 21 | label.server.level=\u670D\u52A1\u5668\u7EA7\u522B\uFF1A 22 | label.global=\u5168\u5C40 23 | label.project=\u9879\u76EE 24 | label.global.hint=\u5168\u5C40\uFF1A\u6240\u6709\u9879\u76EE 25 | label.project.hint=\u9879\u76EE\uFF1A\u4EC5\u5F53\u524D\u9879\u76EE 26 | label.api.key=API \u5BC6\u94A5\uFF08\u53EF\u9009\uFF09\uFF1A 27 | label.reset.to.default=\u91CD\u7F6E\u4E3A\u9ED8\u8BA4\u503C 28 | 29 | # Placeholders 30 | placeholder.json.stdio={"server-name": {"command": "...", "args": [...]}} 31 | placeholder.json.http=HTTP: {"name": {"type": "http", "url": "https://..."}} 32 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/services/GitBranchServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.services 2 | 3 | import com.intellij.openapi.components.Service 4 | import com.intellij.openapi.project.Project 5 | import git4idea.repo.GitRepositoryManager 6 | import mu.KotlinLogging 7 | 8 | private val logger = KotlinLogging.logger {} 9 | 10 | /** 11 | * Git4Idea 实现 - 当 Git4Idea 插件安装时使用 12 | * 13 | * 直接使用 Git4Idea 的公开 API,无需反射 14 | * 此类在 plugin-withGit.xml 中注册,覆盖默认的 NoopGitBranchService 15 | */ 16 | @Service(Service.Level.PROJECT) 17 | class GitBranchServiceImpl(private val project: Project) : GitBranchService { 18 | 19 | override fun getCurrentBranchName(): String? { 20 | return try { 21 | val gitRepoManager = GitRepositoryManager.getInstance(project) 22 | val repo = gitRepoManager.repositories.firstOrNull() 23 | repo?.currentBranch?.name 24 | } catch (e: Exception) { 25 | logger.debug { "Failed to get current branch: ${e.message}" } 26 | null 27 | } 28 | } 29 | 30 | override fun getLocalBranches(): List { 31 | return try { 32 | val gitRepoManager = GitRepositoryManager.getInstance(project) 33 | val repo = gitRepoManager.repositories.firstOrNull() ?: return emptyList() 34 | repo.branches.localBranches.map { it.name } 35 | } catch (e: Exception) { 36 | logger.debug { "Failed to get local branches: ${e.message}" } 37 | emptyList() 38 | } 39 | } 40 | 41 | override fun isGitAvailable(): Boolean { 42 | return try { 43 | val gitRepoManager = GitRepositoryManager.getInstance(project) 44 | gitRepoManager.repositories.isNotEmpty() 45 | } catch (e: Exception) { 46 | false 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/mcp/tools/terminal/TerminalListTool.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.mcp.tools.terminal 2 | 3 | import mu.KotlinLogging 4 | 5 | private val logger = KotlinLogging.logger {} 6 | 7 | /** 8 | * TerminalList 工具 - 列出所有终端会话 9 | */ 10 | class TerminalListTool(private val sessionManager: TerminalSessionManager) { 11 | 12 | /** 13 | * 列出所有终端会话 14 | * 15 | * @param arguments 参数: 16 | * - include_output_preview: Boolean? - 是否包含输出预览(默认 false) 17 | * - preview_lines: Int? - 预览行数(默认 5) 18 | */ 19 | fun execute(arguments: Map): Map { 20 | val includePreview = arguments["include_output_preview"] as? Boolean ?: false 21 | val previewLines = (arguments["preview_lines"] as? Number)?.toInt() ?: 5 22 | 23 | logger.info { "Listing terminal sessions (includePreview: $includePreview)" } 24 | 25 | val sessions = sessionManager.getAllSessions() 26 | 27 | val sessionList = sessions.map { session -> 28 | buildMap { 29 | put("id", session.id) 30 | put("name", session.name) 31 | put("shell_type", session.shellType) 32 | put("is_running", session.hasRunningCommands()) 33 | put("created_at", session.createdAt) 34 | put("last_command_at", session.lastCommandAt) 35 | put("is_background", session.isBackground) 36 | 37 | if (includePreview) { 38 | val output = session.getOutput(previewLines) 39 | put("output_preview", output) 40 | } 41 | } 42 | } 43 | 44 | return mapOf( 45 | "success" to true, 46 | "count" to sessions.size, 47 | "sessions" to sessionList 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/mcp/git/SetCommitMessageTool.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.mcp.git 2 | 3 | import com.intellij.openapi.project.Project 4 | 5 | /** 6 | * 设置 Commit Message 工具 7 | * 8 | * 设置或追加 IDEA Commit 面板中的 commit message 9 | * 返回格式:Markdown 10 | */ 11 | class SetCommitMessageTool(private val project: Project) { 12 | 13 | suspend fun execute(arguments: Map): String { 14 | val message = arguments["message"] as? String 15 | if (message.isNullOrBlank()) { 16 | return buildString { 17 | appendLine("# Set Commit Message") 18 | appendLine() 19 | appendLine("**Error**: `message` parameter is required") 20 | } 21 | } 22 | 23 | val mode = arguments["mode"] as? String ?: "replace" 24 | val append = mode == "append" 25 | 26 | val accessor = CommitPanelAccessor.getInstance(project) 27 | 28 | if (!accessor.isCommitPanelOpen()) { 29 | return buildString { 30 | appendLine("# Set Commit Message") 31 | appendLine() 32 | appendLine("**Error**: Commit panel is not open") 33 | appendLine() 34 | appendLine("Please open the Commit tool window first.") 35 | } 36 | } 37 | 38 | accessor.setCommitMessage(message, append) 39 | 40 | return buildString { 41 | appendLine("# Set Commit Message") 42 | appendLine() 43 | appendLine("**Status**: Success") 44 | appendLine("- **Mode**: ${if (append) "Append" else "Replace"}") 45 | appendLine("- **Message Length**: ${message.length} characters") 46 | appendLine() 47 | appendLine("## Message Set") 48 | appendLine("```") 49 | appendLine(message) 50 | appendLine("```") 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/services/NoopLanguageAnalysisService.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.services 2 | 3 | import com.intellij.openapi.components.Service 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.psi.PsiElement 6 | import com.intellij.psi.search.GlobalSearchScope 7 | 8 | /** 9 | * 语言分析服务的默认空实现 10 | * 11 | * 当 Java 插件不可用时使用此实现。所有方法返回空结果或 false。 12 | * 这确保了插件在 WebStorm、GoLand、PyCharm 等非 Java IDE 中也能正常运行, 13 | * 只是 Java 特有的功能(如类继承搜索)不可用。 14 | * 15 | * 注册方式:在主 plugin.xml 中注册,overrides="false" 16 | */ 17 | @Service(Service.Level.PROJECT) 18 | class NoopLanguageAnalysisService(private val project: Project) : LanguageAnalysisService { 19 | 20 | override fun isAvailable(): Boolean = false 21 | 22 | override fun isClass(element: PsiElement): Boolean = false 23 | 24 | override fun isMethod(element: PsiElement): Boolean = false 25 | 26 | override fun findClassInheritors( 27 | psiClass: PsiElement, 28 | scope: GlobalSearchScope, 29 | deep: Boolean 30 | ): List = emptyList() 31 | 32 | override fun findOverridingMethods( 33 | psiMethod: PsiElement, 34 | scope: GlobalSearchScope, 35 | deep: Boolean 36 | ): List = emptyList() 37 | 38 | override fun getShortNamesCache(): Any? = null 39 | 40 | override fun getAllClassNames(cache: Any?): Array = emptyArray() 41 | 42 | override fun getClassesByName(cache: Any?, name: String, scope: GlobalSearchScope): List = emptyList() 43 | 44 | override fun getAllMethodNames(cache: Any?): Array = emptyArray() 45 | 46 | override fun getMethodsByName(cache: Any?, name: String, scope: GlobalSearchScope): List = emptyList() 47 | 48 | override fun getAllFieldNames(cache: Any?): Array = emptyArray() 49 | 50 | override fun getFieldsByName(cache: Any?, name: String, scope: GlobalSearchScope): List = emptyList() 51 | } 52 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle 配置 2 | org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dfile.encoding=UTF-8 3 | org.gradle.caching=true 4 | 5 | # 守护进程优化配置 6 | org.gradle.daemon=true 7 | # 空闲 10 分钟后自动关闭(避免长时间锁定文件) 8 | org.gradle.daemon.idletimeout=600000 9 | # 最大守护进程数量 10 | org.gradle.daemon.count=2 11 | 12 | # 性能优化 13 | org.gradle.parallel=true 14 | org.gradle.configureondemand=true 15 | # 配置缓存(加速构建,Gradle 8.x+ 推荐) 16 | org.gradle.configuration-cache=true 17 | org.gradle.configuration-cache.problems=warn 18 | 19 | # 文件监控(自动检测文件变化) 20 | org.gradle.vfs.watch=true 21 | 22 | # Kotlin 配置 23 | kotlin.code.style=official 24 | 25 | # ------------------------------------------------------------------ 26 | # IntelliJ Platform Plugin Properties 27 | # 28 | # The following properties are used by the IntelliJ Platform Gradle Plugin 29 | # to configure the project. Read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-properties.html 30 | # ------------------------------------------------------------------ 31 | 32 | # IntelliJ Platform Type and Version 33 | platformType = IU 34 | platformVersion = 2025.3.1 35 | 36 | # Plugin Version 37 | pluginVersion = 1.2.0 38 | 39 | # Plugin Signing 40 | # signPlugin.keystore = 41 | # signPlugin.keystore.password = 42 | # signPlugin.keystore.key.alias = 43 | # signPlugin.keystore.key.password = 44 | 45 | # Publish Plugin 46 | # publishPlugin.token = 47 | 48 | # Java Configuration 49 | javaVersion = 21 50 | 51 | # Plugin Dependencies 52 | # pluginDependencies = 53 | 54 | # Plugin Metadata 55 | pluginGroup = com.asakii 56 | pluginName = Claude Code Plus 57 | pluginSinceBuild = 242 58 | pluginUntilBuild = 253.* 59 | 60 | # OSSRH 发布凭据(已配置在 %USERPROFILE%\.gradle\gradle.properties 中) 61 | # ossrhUsername= 62 | # ossrhPassword= 63 | # 如果需要,可在本机 Gradle 用户配置中覆写以下 GPG 信息 64 | signing.gnupg.executable=C:/Program Files/Git/usr/bin/gpg.exe 65 | # signing.gnupg.keyName= 66 | # signing.gnupg.passphrase= -------------------------------------------------------------------------------- /ai-agent-sdk/src/main/kotlin/com/asakii/ai/agent/sdk/AiAgentStreamBridge.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.ai.agent.sdk 2 | 3 | import com.asakii.ai.agent.sdk.adapter.ClaudeStreamAdapter 4 | import com.asakii.ai.agent.sdk.adapter.CodexStreamAdapter 5 | import com.asakii.ai.agent.sdk.adapter.UiStreamAdapter 6 | import com.asakii.ai.agent.sdk.model.NormalizedStreamEvent 7 | import com.asakii.ai.agent.sdk.model.UiStreamEvent 8 | import com.asakii.claude.agent.sdk.types.Message 9 | import com.asakii.codex.agent.sdk.ThreadEvent 10 | import kotlinx.coroutines.flow.Flow 11 | import kotlinx.coroutines.flow.flow 12 | 13 | /** 14 | * 统一的流式适配器入口,将底层 SDK 的流转换成 UI 可直接渲染的事件。 15 | */ 16 | class AiAgentStreamBridge { 17 | private val claudeAdapter = ClaudeStreamAdapter() 18 | private val codexAdapter = CodexStreamAdapter() 19 | private val uiAdapter = UiStreamAdapter() 20 | 21 | fun fromClaude(messages: Flow): Flow = 22 | toUiEvents(normalizeClaude(messages)) 23 | 24 | fun fromCodex(events: Flow): Flow = 25 | toUiEvents(normalizeCodex(events)) 26 | 27 | fun normalizeClaude(messages: Flow): Flow = flow { 28 | messages.collect { message -> 29 | claudeAdapter.convert(message).forEach { emit(it) } 30 | } 31 | } 32 | 33 | fun normalizeCodex(events: Flow): Flow = flow { 34 | events.collect { event -> 35 | codexAdapter.convert(event).forEach { emit(it) } 36 | } 37 | } 38 | 39 | fun toUiEvents(events: Flow): Flow = flow { 40 | events.collect { normalized -> 41 | uiAdapter.convert(normalized).forEach { emit(it) } 42 | } 43 | } 44 | } 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/ui/title/NewSessionAction.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.ui.title 2 | 3 | import com.asakii.rpc.api.JetBrainsSessionApi 4 | import com.asakii.rpc.api.JetBrainsSessionCommand 5 | import com.asakii.rpc.api.JetBrainsSessionCommandType 6 | import com.intellij.icons.AllIcons 7 | import com.intellij.openapi.actionSystem.AnAction 8 | import com.intellij.openapi.actionSystem.AnActionEvent 9 | import java.util.logging.Logger 10 | 11 | /** 12 | * 新建会话按钮 - 显示在 ToolWindow 标题栏右侧 13 | * 14 | * 点击后: 15 | * - 如果当前会话正在生成中 → 创建新 Tab 16 | * - 否则 → 重置/清空当前会话(不新建 Tab) 17 | */ 18 | class NewSessionAction( 19 | private val sessionApi: JetBrainsSessionApi 20 | ) : AnAction("新建会话", "创建新会话", AllIcons.General.Add) { 21 | 22 | private val logger = Logger.getLogger(NewSessionAction::class.java.name) 23 | 24 | override fun actionPerformed(e: AnActionEvent) { 25 | logger.info("🆕 [NewSessionAction] 点击新建会话按钮") 26 | 27 | // 检查当前会话是否正在生成中 28 | val currentState = sessionApi.getState() 29 | val activeSessionId = currentState?.activeSessionId 30 | val activeSession = currentState?.sessions?.find { it.id == activeSessionId } 31 | val isGenerating = activeSession?.isGenerating == true || activeSession?.isConnecting == true 32 | 33 | if (isGenerating) { 34 | // 当前会话正在生成中,创建新 Tab 35 | logger.info("🆕 [NewSessionAction] 当前会话正在生成,发送 CREATE 命令") 36 | sessionApi.sendCommand(JetBrainsSessionCommand( 37 | type = JetBrainsSessionCommandType.CREATE 38 | )) 39 | } else { 40 | // 当前会话空闲,重置/清空当前会话 41 | logger.info("🆕 [NewSessionAction] 当前会话空闲,发送 RESET 命令") 42 | sessionApi.sendCommand(JetBrainsSessionCommand( 43 | type = JetBrainsSessionCommandType.RESET 44 | )) 45 | } 46 | } 47 | 48 | override fun update(e: AnActionEvent) { 49 | e.presentation.isEnabled = true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/messages/ClaudeCodePlusBundle_ko.properties: -------------------------------------------------------------------------------- 1 | # Claude Code Plus Plugin - Messages Bundle (Korean) 2 | 3 | # MCP Configuration 4 | mcp.settings.notice=\uCC38\uACE0: \uC5EC\uAE30\uC11C \uAD6C\uC131\uD55C MCP \uC11C\uBC84\uB294 Claude Code Plus \uD50C\uB7EC\uADF8\uC778 \uC138\uC158\uC5D0\uB9CC \uC801\uC6A9\uB429\uB2C8\uB2E4. \ 5 | \uB3C5\uB9BD \uC2E4\uD589\uD615 Claude Code CLI\uC5D0\uB294 \uC601\uD5A5\uC744 \uBBF8\uCE58\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. 6 | mcp.settings.custom.warning=\uC2E0\uB8B0\uD560 \uC218 \uC788\uB294 \uC11C\uBC84\uC5D0\uB9CC \uC5F0\uACB0\uD558\uC138\uC694. 7 | 8 | # Agents Configuration 9 | agents.settings.notice=\uCC38\uACE0: \uC5EC\uAE30\uC11C \uAD6C\uC131\uD55C \uC0AC\uC6A9\uC790 \uC815\uC758 \uC5D0\uC774\uC804\uD2B8\uB294 Claude Code Plus \uD50C\uB7EC\uADF8\uC778 \uC138\uC158\uC5D0\uB9CC \uC801\uC6A9\uB429\uB2C8\uB2E4. \ 10 | \uB3C5\uB9BD \uC2E4\uD589\uD615 Claude Code CLI\uC5D0\uB294 \uC601\uD5A5\uC744 \uBBF8\uCE58\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. 11 | 12 | # Dialog titles 13 | dialog.edit.builtin.mcp={0} \uD3B8\uC9D1 14 | dialog.new.mcp.server=\uC0C8 MCP \uC11C\uBC84 15 | dialog.edit.mcp.server=MCP \uC11C\uBC84 \uD3B8\uC9D1 16 | 17 | # Labels 18 | label.enable=\uD65C\uC131\uD654 19 | label.json.configuration=JSON \uAD6C\uC131: 20 | label.appended.system.prompt=\uCD94\uAC00 \uC2DC\uC2A4\uD15C \uD504\uB86C\uD504\uD2B8 (\uC120\uD0DD \uC0AC\uD56D): 21 | label.server.level=\uC11C\uBC84 \uB808\uBCA8: 22 | label.global=\uC804\uC5ED 23 | label.project=\uD504\uB85C\uC81D\uD2B8 24 | label.global.hint=\uC804\uC5ED: \uBAA8\uB4E0 \uD504\uB85C\uC81D\uD2B8 25 | label.project.hint=\uD504\uB85C\uC81D\uD2B8: \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB9CC 26 | label.api.key=API \uD0A4 (\uC120\uD0DD \uC0AC\uD56D): 27 | label.reset.to.default=\uAE30\uBCF8\uAC12\uC73C\uB85C \uC7AC\uC124\uC815 28 | 29 | # Placeholders 30 | placeholder.json.stdio={"server-name": {"command": "...", "args": [...]}} 31 | placeholder.json.http=HTTP: {"name": {"type": "http", "url": "https://..."}} 32 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/test/kotlin/com/asakii/claude/agent/sdk/CanUseToolCallbackTest.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk 2 | 3 | import com.asakii.claude.agent.sdk.types.* 4 | import kotlinx.coroutines.runBlocking 5 | import org.junit.jupiter.api.Test 6 | import java.nio.file.Path 7 | 8 | /** 9 | * 测试 canUseTool 回调参数 10 | */ 11 | class CanUseToolCallbackTest { 12 | 13 | @Test 14 | fun `test canUseTool callback parameters`() = runBlocking { 15 | val workDir = Path.of(System.getProperty("user.dir")) 16 | 17 | val canUseToolCallback: CanUseTool = { toolName, input, toolUseId, context -> 18 | println("=" .repeat(60)) 19 | println("🔐 canUseTool 回调被调用!") 20 | println("=" .repeat(60)) 21 | println("📌 toolName: $toolName") 22 | println("📌 toolUseId: $toolUseId") 23 | println("📌 input: $input") 24 | println("📌 input.keys: ${input.keys}") 25 | println("📌 context: $context") 26 | 27 | // 打印 input 的所有字段 28 | input.forEach { (key, value) -> 29 | println(" input[$key] = $value") 30 | } 31 | 32 | println("=" .repeat(60)) 33 | 34 | // 允许执行 35 | PermissionResultAllow(updatedInput = input) 36 | } 37 | 38 | val options = ClaudeAgentOptions( 39 | cwd = workDir, 40 | permissionMode = PermissionMode.DEFAULT, 41 | maxTurns = 2, 42 | canUseTool = canUseToolCallback 43 | ) 44 | 45 | val client = ClaudeCodeSdkClient(options) 46 | 47 | try { 48 | client.connect() 49 | client.query("在当前目录创建一个名为 test_permission.txt 的文件,内容为 hello") 50 | 51 | client.receiveResponse().collect { message -> 52 | if (message is ResultMessage) { 53 | println("✅ 完成") 54 | } 55 | } 56 | } finally { 57 | client.disconnect() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /frontend/src/services/localeService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 语言服务 - 代理到 vue-i18n 3 | * 保持现有代码兼容 4 | */ 5 | 6 | import { setLocale as i18nSetLocale, getLocale as i18nGetLocale, type SupportedLocale } from '@/i18n' 7 | 8 | export type { SupportedLocale } 9 | 10 | class LocaleService { 11 | /** 12 | * 初始化语言服务(现在不需要做任何事,i18n 已在 main.ts 初始化) 13 | */ 14 | async init(): Promise { 15 | console.log('🌐 Locale initialized from localStorage/browser:', this.getLocale()) 16 | } 17 | 18 | /** 19 | * 获取当前语言 20 | */ 21 | getLocale(): SupportedLocale { 22 | return i18nGetLocale() 23 | } 24 | 25 | /** 26 | * 设置语言 27 | */ 28 | async setLocale(locale: SupportedLocale): Promise { 29 | i18nSetLocale(locale) 30 | console.log('🌐 Locale changed to:', locale) 31 | } 32 | 33 | /** 34 | * 获取语言代码(用于 Element Plus 等库) 35 | */ 36 | getElementPlusLocale(): string { 37 | const localeMap: Record = { 38 | 'zh-CN': 'zh-cn', 39 | 'en-US': 'en', 40 | 'ko-KR': 'ko', 41 | 'ja-JP': 'ja' 42 | } 43 | return localeMap[this.getLocale()] || 'en' 44 | } 45 | 46 | /** 47 | * 是否为中文 48 | */ 49 | isChinese(): boolean { 50 | return this.getLocale() === 'zh-CN' 51 | } 52 | 53 | /** 54 | * 是否为英文 55 | */ 56 | isEnglish(): boolean { 57 | return this.getLocale() === 'en-US' 58 | } 59 | 60 | /** 61 | * 是否为韩语 62 | */ 63 | isKorean(): boolean { 64 | return this.getLocale() === 'ko-KR' 65 | } 66 | 67 | /** 68 | * 是否为日语 69 | */ 70 | isJapanese(): boolean { 71 | return this.getLocale() === 'ja-JP' 72 | } 73 | 74 | /** 75 | * 获取所有支持的语言 76 | */ 77 | getSupportedLocales(): Array<{ value: SupportedLocale; label: string }> { 78 | return [ 79 | { value: 'zh-CN', label: '简体中文' }, 80 | { value: 'en-US', label: 'English' }, 81 | { value: 'ko-KR', label: '한국어' }, 82 | { value: 'ja-JP', label: '日本語' } 83 | ] 84 | } 85 | } 86 | 87 | // 单例 88 | const localeService = new LocaleService() 89 | 90 | export default localeService 91 | -------------------------------------------------------------------------------- /frontend/src/utils/scrollBoost.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * JCEF 滚动增强模块 3 | * 4 | * 解决 JCEF(JetBrains Chromium Embedded Framework)中滚动不灵敏的问题。 5 | * 通过拦截 wheel 事件并手动控制滚动量来增强滚动体验。 6 | * 7 | * @see https://magpcss.org/ceforum/viewtopic.php?f=17&t=18353 8 | */ 9 | 10 | /** 11 | * 从 URL 参数获取滚动倍增系数 12 | * @returns 倍增系数,默认为 1(不增强) 13 | */ 14 | export function getScrollMultiplier(): number { 15 | const params = new URLSearchParams(window.location.search) 16 | const value = params.get('scrollMultiplier') 17 | 18 | if (value) { 19 | const num = parseFloat(value) 20 | if (!isNaN(num) && num > 0) { 21 | return num 22 | } 23 | } 24 | 25 | return 1 // 默认不增强 26 | } 27 | 28 | /** 29 | * 初始化滚动增强 30 | * 仅当倍增系数 > 1 时才启用 31 | */ 32 | export function initScrollBoost(): void { 33 | const multiplier = getScrollMultiplier() 34 | 35 | if (multiplier <= 1) { 36 | console.log('🖱️ Scroll boost disabled (multiplier <= 1)') 37 | return 38 | } 39 | 40 | console.log(`🖱️ Scroll boost enabled with multiplier: ${multiplier}`) 41 | 42 | document.addEventListener('wheel', (e: WheelEvent) => { 43 | // 找到最近的可滚动容器 44 | let target = e.target as HTMLElement | null 45 | 46 | while (target && target !== document.body) { 47 | const style = getComputedStyle(target) 48 | const overflowY = style.overflowY 49 | const isScrollableY = 50 | (overflowY === 'auto' || overflowY === 'scroll') && 51 | target.scrollHeight > target.clientHeight 52 | 53 | if (isScrollableY) { 54 | e.preventDefault() 55 | // 应用倍增系数(减去 1 是因为浏览器会执行默认滚动) 56 | // 但由于 preventDefault() 阻止了默认行为,所以直接使用完整倍数 57 | target.scrollTop += e.deltaY * multiplier 58 | return 59 | } 60 | 61 | target = target.parentElement 62 | } 63 | 64 | // 回退到 body 滚动 65 | if (document.body.scrollHeight > document.body.clientHeight) { 66 | e.preventDefault() 67 | document.body.scrollTop += e.deltaY * multiplier 68 | document.documentElement.scrollTop += e.deltaY * multiplier 69 | } 70 | }, { passive: false }) 71 | } 72 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/handlers/ReadToolHandler.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.handlers 2 | 3 | import com.asakii.plugin.services.IdeaPlatformService 4 | import com.asakii.plugin.types.ReadToolDetail 5 | import com.asakii.plugin.types.LegacyToolCall 6 | import com.asakii.plugin.types.ToolResult 7 | import com.intellij.openapi.diagnostic.Logger 8 | import com.intellij.openapi.project.Project 9 | 10 | /** 11 | * Read 工具点击处理器 12 | * 在 IDEA 编辑器中打开文件,支持行号定位和文本选择 13 | * 14 | * 使用 IdeaPlatformService 统一服务,简化实现 15 | */ 16 | class ReadToolHandler : ToolClickHandler { 17 | 18 | companion object { 19 | private val logger = Logger.getInstance(ReadToolHandler::class.java) 20 | } 21 | 22 | override fun canHandle(toolCall: LegacyToolCall): Boolean { 23 | return false // 临时禁用 24 | } 25 | 26 | override fun handleToolClick( 27 | toolCall: LegacyToolCall, 28 | project: Project?, 29 | config: ToolClickConfig 30 | ): Boolean { 31 | if (project == null) { 32 | logger.info("ReadToolHandler: Project is null, 无法使用 IDE 集成") 33 | return false 34 | } 35 | 36 | if (config.alwaysExpand) { 37 | logger.info("ReadToolHandler: 配置为总是展开,跳过 IDE 集成") 38 | return false 39 | } 40 | 41 | // 临时简化处理 - 待重构后重新实现 42 | return false 43 | } 44 | 45 | private fun buildSelectionRange( 46 | detail: ReadToolDetail, 47 | content: String? 48 | ): IdeaPlatformService.SelectionRange? { 49 | val offset = detail.offset ?: return null 50 | if (offset < 0) return null 51 | 52 | val limit = detail.limit 53 | val length = when { 54 | limit != null && limit > 0 -> limit 55 | !content.isNullOrEmpty() -> content.length 56 | else -> return null 57 | } 58 | 59 | if (length <= 0) return null 60 | 61 | val end = offset + length 62 | if (end <= offset) return null 63 | 64 | return IdeaPlatformService.SelectionRange(offset, end) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import typescript from '@typescript-eslint/eslint-plugin' 3 | import tsParser from '@typescript-eslint/parser' 4 | import vue from 'eslint-plugin-vue' 5 | import vueParser from 'vue-eslint-parser' 6 | import globals from 'globals' 7 | 8 | export default [ 9 | // 基础推荐规则 10 | js.configs.recommended, 11 | 12 | // TypeScript 文件 13 | { 14 | files: ['**/*.ts', '**/*.tsx', '**/*.vue'], 15 | languageOptions: { 16 | parser: vueParser, 17 | parserOptions: { 18 | parser: tsParser, 19 | ecmaVersion: 'latest', 20 | sourceType: 'module', 21 | }, 22 | globals: { 23 | ...globals.browser, 24 | ...globals.node, 25 | }, 26 | }, 27 | plugins: { 28 | '@typescript-eslint': typescript, 29 | vue, 30 | }, 31 | rules: { 32 | // Vue 规则 33 | ...vue.configs['vue3-recommended'].rules, 34 | 'vue/multi-word-component-names': 'off', // 允许单词组件名 35 | 'vue/no-v-html': 'off', // Markdown 渲染必须用 v-html 36 | 'vue/require-default-prop': 'off', // 不强制默认值 37 | 'vue/no-setup-props-destructure': 'off', // 允许 props 解构 38 | 'vue/one-component-per-file': 'off', // 允许子组件在同一文件 39 | 'vue/no-template-shadow': 'off', // 允许合理的变量名重用 40 | 41 | // TypeScript 规则 42 | ...typescript.configs.recommended.rules, 43 | '@typescript-eslint/no-explicit-any': 'off', // Bridge 接口需要 any 44 | '@typescript-eslint/no-unused-vars': ['warn', { 45 | argsIgnorePattern: '^_', 46 | varsIgnorePattern: '^_', 47 | destructuredArrayIgnorePattern: '^_' 48 | }], 49 | '@typescript-eslint/no-non-null-assertion': 'off', // 允许 ! 断言 50 | 51 | // 通用规则 52 | 'no-console': 'off', // 开发时允许 console 53 | 'no-debugger': 'warn', // debugger 警告 54 | 'prefer-const': 'warn', 55 | 'no-unused-vars': 'off' // 使用 TS 版本的规则 56 | }, 57 | }, 58 | 59 | // 忽略文件 60 | { 61 | ignores: [ 62 | 'dist/**', 63 | 'node_modules/**', 64 | '*.config.js', 65 | 'coverage/**', 66 | ], 67 | }, 68 | ] 69 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/types/UiModels.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.types 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | /** 6 | * UI 模型类型定义(向后兼容) 7 | * 8 | * 这些类型用于 JetBrains 插件中的 UI 层 9 | */ 10 | 11 | /** 12 | * AI 模型枚举 13 | */ 14 | enum class AiModel { 15 | SONNET, 16 | OPUS, 17 | HAIKU, 18 | OPUS_PLAN 19 | } 20 | 21 | /** 22 | * 权限模式枚举(插件 UI 用) 23 | */ 24 | enum class UiPermissionMode { 25 | DEFAULT, 26 | ACCEPT, 27 | BYPASS, 28 | PLAN 29 | } 30 | 31 | /** 32 | * 消息角色 33 | */ 34 | enum class MessageRole { 35 | USER, 36 | ASSISTANT, 37 | SYSTEM, 38 | ERROR 39 | } 40 | 41 | /** 42 | * 消息状态 43 | */ 44 | enum class MessageStatus { 45 | PENDING, 46 | STREAMING, 47 | COMPLETE, 48 | ERROR 49 | } 50 | 51 | /** 52 | * Token 使用信息(插件 UI 用,与 SDK 的 TokenUsage 不同) 53 | */ 54 | @Serializable 55 | data class UiTokenUsage( 56 | val inputTokens: Int = 0, 57 | val outputTokens: Int = 0, 58 | val cacheCreationTokens: Int = 0, 59 | val cacheReadTokens: Int = 0, 60 | val totalTokens: Int = 0 61 | ) 62 | 63 | /** 64 | * 会话对象(旧版,向后兼容) 65 | */ 66 | data class SessionObject( 67 | val id: String, 68 | val name: String, 69 | val createdAt: Long, 70 | val modelId: String? 71 | ) 72 | 73 | /** 74 | * 增强消息(旧版,向后兼容) 75 | */ 76 | data class EnhancedMessage( 77 | val id: String, 78 | val role: MessageRole, 79 | val content: String, 80 | val timestamp: Long, 81 | val contexts: List = emptyList(), 82 | val tokenUsage: UiTokenUsage? = null 83 | ) 84 | 85 | /** 86 | * 旧版 ToolCall(向后兼容) 87 | * 现在是 ToolCallItem 的别名 88 | */ 89 | typealias ToolCall = ToolCallItem 90 | 91 | /** 92 | * 旧版工具调用数据类(用于适配器层) 93 | */ 94 | data class LegacyToolCall( 95 | val name: String, 96 | val id: String, 97 | val status: String, 98 | val input: Map = emptyMap(), 99 | val result: String? = null, 100 | val viewModel: Any? = null 101 | ) 102 | 103 | -------------------------------------------------------------------------------- /ai-agent-proto/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.google.protobuf.gradle.* 2 | 3 | plugins { 4 | kotlin("jvm") 5 | id("com.google.protobuf") version "0.9.4" 6 | `java-library` 7 | `maven-publish` 8 | } 9 | 10 | group = "com.asakii" 11 | version = "1.0-SNAPSHOT" 12 | 13 | val protobufVersion = "3.25.3" 14 | 15 | dependencies { 16 | // Protobuf 运行时 (显式声明 protobuf-java 以确保 OrBuilder 接口可被下游模块访问) 17 | api("com.google.protobuf:protobuf-java:$protobufVersion") 18 | api("com.google.protobuf:protobuf-kotlin:$protobufVersion") 19 | api("com.google.protobuf:protobuf-java-util:$protobufVersion") 20 | 21 | // Kotlin 协程(用于未来的 gRPC 支持) 22 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${rootProject.extra["coroutinesVersion"]}") 23 | } 24 | 25 | protobuf { 26 | protoc { 27 | artifact = "com.google.protobuf:protoc:$protobufVersion" 28 | } 29 | 30 | generateProtoTasks { 31 | all().forEach { task -> 32 | task.builtins { 33 | // 生成 Kotlin DSL 扩展(java 是默认的,不需要显式添加) 34 | create("kotlin") 35 | } 36 | } 37 | } 38 | } 39 | 40 | // 确保 proto 生成的代码被正确包含在源码集中 41 | sourceSets { 42 | main { 43 | proto { 44 | srcDir("src/main/proto") 45 | } 46 | } 47 | } 48 | 49 | // 将生成的 Java 代码添加到编译路径 50 | java { 51 | sourceSets { 52 | main { 53 | java.srcDirs( 54 | "build/generated/source/proto/main/java", 55 | "build/generated/source/proto/main/kotlin" 56 | ) 57 | } 58 | } 59 | } 60 | 61 | tasks.withType { 62 | dependsOn("generateProto") 63 | } 64 | 65 | // 处理资源文件重复问题 66 | tasks.withType { 67 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 68 | } 69 | 70 | // 统一 JVM 版本为 17(与其他模块保持一致) 71 | kotlin { 72 | jvmToolchain(17) 73 | } 74 | 75 | publishing { 76 | publications { 77 | create("maven") { 78 | from(components["kotlin"]) 79 | artifactId = "ai-agent-proto" 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/config/PluginConfig.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.config 2 | 3 | import com.intellij.openapi.diagnostic.Logger 4 | 5 | /** 6 | * 插件配置(动态插件兼容版本) 7 | * 8 | * 使用内部状态替代 System.setProperty,支持动态加载/卸载。 9 | */ 10 | object PluginConfig { 11 | 12 | private val logger = Logger.getInstance(PluginConfig::class.java) 13 | 14 | /** 15 | * Claude 命令是否可用 16 | */ 17 | @Volatile 18 | var isClaudeAvailable: Boolean = false 19 | private set 20 | 21 | /** 22 | * Claude 命令检查结果 23 | */ 24 | @Volatile 25 | var claudeCheckResult: String = "" 26 | private set 27 | 28 | /** 29 | * 检查 claude 命令是否可用 30 | * @return Pair<是否可用, 详细信息> 31 | */ 32 | fun checkClaudeCommand(): Pair { 33 | return try { 34 | val osName = System.getProperty("os.name").lowercase() 35 | val command = if (osName.contains("windows")) { 36 | listOf("cmd", "/c", "claude", "--version") 37 | } else { 38 | listOf("/bin/bash", "-c", "claude --version") 39 | } 40 | 41 | val process = ProcessBuilder(command) 42 | .redirectErrorStream(true) 43 | .start() 44 | 45 | val output = process.inputStream.bufferedReader().readText() 46 | val exitCode = process.waitFor() 47 | 48 | if (exitCode == 0) { 49 | Pair(true, "Claude CLI 可用: ${output.trim()}") 50 | } else { 51 | Pair(false, "Claude CLI 执行失败 (退出码: $exitCode): ${output.trim()}") 52 | } 53 | } catch (e: Exception) { 54 | Pair(false, "无法执行 claude 命令: ${e.message}") 55 | } 56 | } 57 | 58 | /** 59 | * 设置环境并检查 claude 命令可用性 60 | * 不再修改全局系统属性,改用内部状态 61 | */ 62 | fun setupEnvironment() { 63 | val (isAvailable, message) = checkClaudeCommand() 64 | 65 | // 使用内部状态替代 System.setProperty 66 | isClaudeAvailable = isAvailable 67 | claudeCheckResult = message 68 | 69 | logger.info("Claude 命令检查: $message") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/types/ToolConstants.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.types 2 | 3 | /** 4 | * 工具类型常量 5 | * 6 | * 对应 frontend/src/constants/toolTypes.ts 7 | */ 8 | object ToolConstants { 9 | // 文件操作工具 10 | const val READ = "Read" 11 | const val WRITE = "Write" 12 | const val EDIT = "Edit" 13 | const val MULTI_EDIT = "MultiEdit" 14 | const val NOTEBOOK_EDIT = "NotebookEdit" 15 | 16 | // 命令执行工具 17 | const val BASH = "Bash" 18 | const val BASH_OUTPUT = "BashOutput" 19 | const val TASK = "Task" 20 | const val KILL_SHELL = "KillShell" 21 | 22 | // 搜索工具 23 | const val GREP = "Grep" 24 | const val GLOB = "Glob" 25 | 26 | // Web 工具 27 | const val WEB_SEARCH = "WebSearch" 28 | const val WEB_FETCH = "WebFetch" 29 | 30 | // 任务和计划工具 31 | const val TODO_WRITE = "TodoWrite" 32 | const val EXIT_PLAN_MODE = "ExitPlanMode" 33 | 34 | // 用户交互工具 35 | const val ASK_USER_QUESTION = "AskUserQuestion" 36 | 37 | // 技能和命令工具 38 | const val SKILL = "Skill" 39 | const val SLASH_COMMAND = "SlashCommand" 40 | 41 | // MCP 工具 42 | const val LIST_MCP_RESOURCES = "ListMcpResourcesTool" 43 | const val READ_MCP_RESOURCE = "ReadMcpResourceTool" 44 | 45 | // 工具名称映射 46 | val TOOL_NAME_TO_TYPE = mapOf( 47 | "Read" to READ, 48 | "Write" to WRITE, 49 | "Edit" to EDIT, 50 | "MultiEdit" to MULTI_EDIT, 51 | "NotebookEdit" to NOTEBOOK_EDIT, 52 | "Bash" to BASH, 53 | "BashOutput" to BASH_OUTPUT, 54 | "Task" to TASK, 55 | "KillShell" to KILL_SHELL, 56 | "Grep" to GREP, 57 | "Glob" to GLOB, 58 | "WebSearch" to WEB_SEARCH, 59 | "WebFetch" to WEB_FETCH, 60 | "TodoWrite" to TODO_WRITE, 61 | "ExitPlanMode" to EXIT_PLAN_MODE, 62 | "AskUserQuestion" to ASK_USER_QUESTION, 63 | "Skill" to SKILL, 64 | "SlashCommand" to SLASH_COMMAND, 65 | "ListMcpResourcesTool" to LIST_MCP_RESOURCES, 66 | "ReadMcpResourceTool" to READ_MCP_RESOURCE 67 | ) 68 | } 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /frontend/src/i18n.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * vue-i18n 配置 3 | * 4 | * 语言同步策略: 5 | * - 浏览器环境:localStorage > 浏览器语言 > 默认 en-US 6 | * - IDEA 环境 + 主题同步:同步 IDEA 语言设置 7 | * - IDEA 环境 + 无主题同步:使用 localStorage(用户自选) 8 | */ 9 | import { createI18n } from 'vue-i18n' 10 | import zhCN from './locales/zh-CN' 11 | import enUS from './locales/en-US' 12 | import koKR from './locales/ko-KR' 13 | import jaJP from './locales/ja-JP' 14 | 15 | export type SupportedLocale = 'zh-CN' | 'en-US' | 'ko-KR' | 'ja-JP' 16 | 17 | const LOCALE_STORAGE_KEY = 'claude-code-plus-locale' 18 | const SUPPORTED_LOCALES: SupportedLocale[] = ['zh-CN', 'en-US', 'ko-KR', 'ja-JP'] 19 | 20 | /** 21 | * 将任意语言代码标准化为支持的语言 22 | */ 23 | export function normalizeLocale(locale: string): SupportedLocale { 24 | const normalized = locale.toLowerCase().replace('_', '-') 25 | 26 | if (normalized.startsWith('zh')) return 'zh-CN' 27 | if (normalized.startsWith('ko')) return 'ko-KR' 28 | if (normalized.startsWith('ja')) return 'ja-JP' 29 | return 'en-US' 30 | } 31 | 32 | // 检测浏览器语言 33 | function detectBrowserLocale(): SupportedLocale { 34 | if (typeof window === 'undefined') return 'en-US' 35 | 36 | // 优先使用 localStorage 保存的语言 37 | const savedLocale = localStorage.getItem(LOCALE_STORAGE_KEY) 38 | if (savedLocale && SUPPORTED_LOCALES.includes(savedLocale as SupportedLocale)) { 39 | return savedLocale as SupportedLocale 40 | } 41 | 42 | // 其次检测浏览器语言 43 | const browserLang = navigator.language || (navigator as any).userLanguage || 'en-US' 44 | return normalizeLocale(browserLang) 45 | } 46 | 47 | export const i18n = createI18n({ 48 | legacy: false, // 使用 Composition API 模式 49 | locale: detectBrowserLocale(), 50 | fallbackLocale: 'en-US', 51 | messages: { 52 | 'zh-CN': zhCN, 53 | 'en-US': enUS, 54 | 'ko-KR': koKR, 55 | 'ja-JP': jaJP 56 | } 57 | }) 58 | 59 | // 导出便捷方法 60 | export function setLocale(locale: SupportedLocale) { 61 | i18n.global.locale.value = locale 62 | // 持久化到 localStorage 63 | if (typeof window !== 'undefined') { 64 | localStorage.setItem(LOCALE_STORAGE_KEY, locale) 65 | } 66 | } 67 | 68 | export function getLocale(): SupportedLocale { 69 | return i18n.global.locale.value as SupportedLocale 70 | } 71 | -------------------------------------------------------------------------------- /ai-agent-server/src/main/kotlin/com/asakii/server/logging/LazyLogMessage.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.server.logging 2 | 3 | import kotlinx.serialization.encodeToString 4 | import kotlinx.serialization.json.Json 5 | 6 | /** 7 | * 延迟格式化的日志消息包装器 8 | * 9 | * 用于将消息格式化延迟到日志线程中执行,避免阻塞工作线程。 10 | * 配合 Logback AsyncAppender 使用,toString() 会在异步日志线程中被调用。 11 | * 12 | * 使用方式: 13 | * ```kotlin 14 | * logger.info { LazyLogMessage { formatMyMessage(message) } } 15 | * ``` 16 | */ 17 | class LazyLogMessage(private val supplier: () -> String) { 18 | override fun toString(): String = supplier() 19 | } 20 | 21 | /** 22 | * 延迟 JSON 序列化的日志消息包装器 23 | * 24 | * 自动将对象序列化为 JSON 字符串,格式化在日志线程中执行。 25 | * 26 | * 使用方式: 27 | * ```kotlin 28 | * logger.info { LazyJsonMessage(myObject) } 29 | * ``` 30 | */ 31 | class LazyJsonMessage( 32 | private val value: T, 33 | private val serializer: (T) -> String 34 | ) { 35 | override fun toString(): String = serializer(value) 36 | 37 | companion object { 38 | @PublishedApi 39 | internal val json = Json { 40 | prettyPrint = false 41 | ignoreUnknownKeys = true 42 | encodeDefaults = true 43 | } 44 | 45 | /** 46 | * 使用默认 JSON 序列化器 47 | */ 48 | inline fun of(value: T): LazyJsonMessage { 49 | return LazyJsonMessage(value) { json.encodeToString(it) } 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * 延迟格式化的日志消息构建器 56 | * 57 | * 提供更灵活的日志消息构建方式 58 | */ 59 | class LazyLogBuilder { 60 | private val parts = mutableListOf<() -> String>() 61 | 62 | fun append(value: String): LazyLogBuilder { 63 | parts.add { value } 64 | return this 65 | } 66 | 67 | fun append(supplier: () -> String): LazyLogBuilder { 68 | parts.add(supplier) 69 | return this 70 | } 71 | 72 | fun appendJson(value: Any?): LazyLogBuilder { 73 | parts.add { value?.toString() ?: "null" } 74 | return this 75 | } 76 | 77 | override fun toString(): String = parts.joinToString("") { it() } 78 | } 79 | 80 | /** 81 | * 创建延迟格式化的日志消息 82 | */ 83 | fun lazyLog(supplier: () -> String): LazyLogMessage = LazyLogMessage(supplier) 84 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/kotlin/com/asakii/claude/agent/sdk/examples/JointTestClient.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk.examples 2 | 3 | import com.asakii.claude.agent.sdk.ClaudeCodeSdkClient 4 | import com.asakii.claude.agent.sdk.types.ClaudeAgentOptions 5 | import kotlinx.coroutines.flow.collect 6 | import kotlinx.coroutines.launch 7 | import kotlinx.coroutines.runBlocking 8 | import java.util.UUID 9 | 10 | fun main() = runBlocking { 11 | val serverUrl = "http://127.0.0.1:52244" // Port from the running server 12 | println("--- Joint Test Client ---") 13 | println("Connecting to server at: $serverUrl") 14 | 15 | val client = ClaudeCodeSdkClient( 16 | ClaudeAgentOptions( 17 | model = "claude-sonnet-4-5-20250929", 18 | mcpServers = mapOf( 19 | "default" to com.asakii.claude.agent.sdk.types.McpHttpServerConfig(url = serverUrl) 20 | ) 21 | ) 22 | ) 23 | 24 | try { 25 | client.connect() 26 | println("✅ Client connected successfully.") 27 | 28 | val sessionId = "joint-test-${UUID.randomUUID()}" 29 | println("🔹 Using Session ID: $sessionId") 30 | 31 | // Launch a coroutine to listen for all incoming messages 32 | val job = launch { 33 | println("🎧 Listening for messages from server...") 34 | client.receiveResponse().collect { message -> 35 | println("\n<<< Received from server:") 36 | println(message) 37 | } 38 | } 39 | 40 | // Give the listener a moment to start up 41 | kotlinx.coroutines.delay(500) 42 | 43 | println("\n>>> Sending query: 'Hello'...") 44 | client.query("Hello", sessionId) 45 | 46 | // Wait for a while to see all messages 47 | println("\n⏳ Waiting for 10 seconds to observe all messages...") 48 | kotlinx.coroutines.delay(10000) 49 | 50 | job.cancel() // Stop listening 51 | 52 | } catch (e: Exception) { 53 | println("\n❌ An error occurred: ${e.message}") 54 | e.printStackTrace() 55 | } finally { 56 | if (client.isConnected()) { 57 | client.disconnect() 58 | println("🔌 Client disconnected.") 59 | } 60 | } 61 | println("--- Test Finished ---") 62 | } 63 | 64 | -------------------------------------------------------------------------------- /frontend/src/components/tools/DiffViewer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 47 | 48 | 98 | 99 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/resources/messages/ClaudeCodePlusBundle_ja.properties: -------------------------------------------------------------------------------- 1 | # Claude Code Plus Plugin - Messages Bundle (Japanese) 2 | 3 | # MCP Configuration 4 | mcp.settings.notice=\u6CE8\u610F\uFF1A\u3053\u3053\u3067\u8A2D\u5B9A\u3057\u305F MCP \u30B5\u30FC\u30D0\u30FC\u306F Claude Code Plus \u30D7\u30E9\u30B0\u30A4\u30F3\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306B\u306E\u307F\u9069\u7528\u3055\u308C\u307E\u3059\u3002\ 5 | \u30B9\u30BF\u30F3\u30C9\u30A2\u30ED\u30F3\u306E Claude Code CLI \u306B\u306F\u5F71\u97FF\u3057\u307E\u305B\u3093\u3002 6 | mcp.settings.custom.warning=\u4FE1\u983C\u3067\u304D\u308B\u30B5\u30FC\u30D0\u30FC\u306B\u306E\u307F\u63A5\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002 7 | 8 | # Agents Configuration 9 | agents.settings.notice=\u6CE8\u610F\uFF1A\u3053\u3053\u3067\u8A2D\u5B9A\u3057\u305F\u30AB\u30B9\u30BF\u30E0\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306F Claude Code Plus \u30D7\u30E9\u30B0\u30A4\u30F3\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306B\u306E\u307F\u9069\u7528\u3055\u308C\u307E\u3059\u3002\ 10 | \u30B9\u30BF\u30F3\u30C9\u30A2\u30ED\u30F3\u306E Claude Code CLI \u306B\u306F\u5F71\u97FF\u3057\u307E\u305B\u3093\u3002 11 | 12 | # Dialog titles 13 | dialog.edit.builtin.mcp=\u7DE8\u96C6 {0} 14 | dialog.new.mcp.server=\u65B0\u898F MCP \u30B5\u30FC\u30D0\u30FC 15 | dialog.edit.mcp.server=MCP \u30B5\u30FC\u30D0\u30FC\u306E\u7DE8\u96C6 16 | 17 | # Labels 18 | label.enable=\u6709\u52B9 19 | label.json.configuration=JSON \u8A2D\u5B9A\uFF1A 20 | label.appended.system.prompt=\u8FFD\u52A0\u30B7\u30B9\u30C6\u30E0\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08\u4EFB\u610F\uFF09\uFF1A 21 | label.server.level=\u30B5\u30FC\u30D0\u30FC\u30EC\u30D9\u30EB\uFF1A 22 | label.global=\u30B0\u30ED\u30FC\u30D0\u30EB 23 | label.project=\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 24 | label.global.hint=\u30B0\u30ED\u30FC\u30D0\u30EB\uFF1A\u5168\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8 25 | label.project.hint=\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\uFF1A\u73FE\u5728\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u307F 26 | label.api.key=API \u30AD\u30FC\uFF08\u4EFB\u610F\uFF09\uFF1A 27 | label.reset.to.default=\u30C7\u30D5\u30A9\u30EB\u30C8\u306B\u30EA\u30BB\u30C3\u30C8 28 | 29 | # Placeholders 30 | placeholder.json.stdio={"server-name": {"command": "...", "args": [...]}} 31 | placeholder.json.http=HTTP: {"name": {"type": "http", "url": "https://..."}} 32 | -------------------------------------------------------------------------------- /frontend/src/components/tools/EnterPlanModeToolDisplay.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 48 | 49 | 80 | -------------------------------------------------------------------------------- /frontend/src/composables/useInputResize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 输入框大小调整的 composable 3 | * 处理拖拽调整输入框高度 4 | */ 5 | import { ref } from 'vue' 6 | 7 | /** 输入框最小高度(px) */ 8 | export const INPUT_MIN_HEIGHT = 110 9 | /** 输入框最大高度(px) */ 10 | export const INPUT_MAX_HEIGHT = 500 11 | 12 | export interface UseInputResizeOptions { 13 | /** 最小高度 */ 14 | minHeight?: number 15 | /** 最大高度 */ 16 | maxHeight?: number 17 | /** 初始高度(null 表示自动) */ 18 | initialHeight?: number | null 19 | } 20 | 21 | export function useInputResize(options: UseInputResizeOptions = {}) { 22 | const { 23 | minHeight = 110, 24 | maxHeight = 500, 25 | initialHeight = null 26 | } = options 27 | 28 | const containerHeight = ref(initialHeight) 29 | const isResizing = ref(false) 30 | 31 | /** 32 | * 开始拖拽调整大小 33 | */ 34 | function startResize(event: MouseEvent) { 35 | event.preventDefault() 36 | isResizing.value = true 37 | const startY = event.clientY 38 | 39 | // 首次拖拽时获取当前实际高度 40 | const container = (event.target as HTMLElement).closest('.unified-chat-input-container') as HTMLElement 41 | const startHeight = containerHeight.value ?? container?.offsetHeight ?? 120 42 | 43 | const onMouseMove = (e: MouseEvent) => { 44 | // 向上拖动增加高度,向下拖动减少高度 45 | const deltaY = startY - e.clientY 46 | const newHeight = Math.min(maxHeight, Math.max(minHeight, startHeight + deltaY)) 47 | containerHeight.value = newHeight 48 | } 49 | 50 | const onMouseUp = () => { 51 | isResizing.value = false 52 | document.removeEventListener('mousemove', onMouseMove) 53 | document.removeEventListener('mouseup', onMouseUp) 54 | } 55 | 56 | document.addEventListener('mousemove', onMouseMove) 57 | document.addEventListener('mouseup', onMouseUp) 58 | } 59 | 60 | /** 61 | * 重置高度为自动 62 | */ 63 | function resetHeight() { 64 | containerHeight.value = null 65 | } 66 | 67 | /** 68 | * 设置固定高度 69 | */ 70 | function setHeight(height: number) { 71 | containerHeight.value = Math.min(maxHeight, Math.max(minHeight, height)) 72 | } 73 | 74 | return { 75 | // 状态 76 | containerHeight, 77 | isResizing, 78 | // 配置 79 | minHeight, 80 | maxHeight, 81 | // 方法 82 | startResize, 83 | resetHeight, 84 | setHeight 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /claude-agent-sdk/src/main/kotlin/com/asakii/claude/agent/sdk/plugin/services/SessionStateSyncImpl.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk.plugin.services 2 | 3 | import com.asakii.claude.agent.sdk.plugin.interfaces.SessionStateSync 4 | import com.asakii.claude.agent.sdk.plugin.types.SessionState 5 | import com.asakii.claude.agent.sdk.plugin.types.SessionUpdate 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.flowOf 8 | 9 | /** 10 | * SessionStateSync 接口的简化实现 11 | */ 12 | class SessionStateSyncImpl : SessionStateSync { 13 | 14 | override suspend fun saveSessionState(sessionId: String, state: SessionState) { 15 | // 简化实现 - 暂时不做持久化 16 | } 17 | 18 | override suspend fun loadSessionState(sessionId: String): SessionState? { 19 | // 简化实现 - 返回空状态 20 | return null 21 | } 22 | 23 | override fun observeSessionUpdates(sessionId: String): Flow { 24 | // 简化实现 - 返回空流 25 | return flowOf() 26 | } 27 | 28 | override fun observeProjectUpdates(projectPath: String): Flow> { 29 | // 简化实现 - 返回空流 30 | return flowOf(emptyMap()) 31 | } 32 | 33 | override suspend fun startBackgroundExecution( 34 | sessionId: String?, 35 | projectPath: String, 36 | prompt: String, 37 | options: Any 38 | ): Flow { 39 | // 简化实现 - 返回空流 40 | return flowOf() 41 | } 42 | 43 | override suspend fun terminateBackgroundSession(sessionId: String) { 44 | // 简化实现 - 无操作 45 | } 46 | 47 | override suspend fun isSessionRunningInBackground(sessionId: String): Boolean { 48 | // 简化实现 - 总是返回 false 49 | return false 50 | } 51 | 52 | override suspend fun getBackgroundServiceStats(): Map { 53 | // 简化实现 - 返回空统计 54 | return emptyMap() 55 | } 56 | 57 | override suspend fun recoverSessionHistory(sessionId: String, projectPath: String): Boolean { 58 | // 简化实现 - 总是返回 false 59 | return false 60 | } 61 | 62 | // 额外的方法以满足现有代码的需求 63 | fun observeProjectSessionUpdates(projectPath: String): Flow> { 64 | return observeProjectUpdates(projectPath) 65 | } 66 | 67 | fun getServiceStats(): Map { 68 | return emptyMap() 69 | } 70 | } -------------------------------------------------------------------------------- /claude-agent-sdk/src/test/kotlin/com/asakii/claude/agent/sdk/PreToolUseHookTest.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.claude.agent.sdk 2 | 3 | import com.asakii.claude.agent.sdk.types.* 4 | import kotlinx.coroutines.runBlocking 5 | import kotlinx.serialization.json.* 6 | import org.junit.jupiter.api.Test 7 | import org.junit.jupiter.api.Assertions.* 8 | import java.nio.file.Path 9 | 10 | /** 11 | * 测试 PreToolUse Hook 功能 12 | */ 13 | class PreToolUseHookTest { 14 | 15 | @Test 16 | fun `test hook receives toolUseId`() = runBlocking { 17 | val workDir = Path.of(System.getProperty("user.dir")) 18 | val hookCalls = mutableListOf>>() 19 | 20 | val preToolUseHook: HookCallback = { input, toolUseId, _ -> 21 | println("🎣 Hook called: toolUseId=$toolUseId, input=$input") 22 | hookCalls.add(toolUseId to input) 23 | HookJSONOutput(decision = null) 24 | } 25 | 26 | val hooks = mapOf( 27 | HookEvent.PRE_TOOL_USE to listOf( 28 | HookMatcher(matcher = "*", hooks = listOf(preToolUseHook)) 29 | ) 30 | ) 31 | 32 | val options = ClaudeAgentOptions( 33 | cwd = workDir, 34 | permissionMode = PermissionMode.BYPASS_PERMISSIONS, 35 | maxTurns = 2, 36 | hooks = hooks 37 | ) 38 | 39 | println("📋 options.hooks: ${options.hooks}") 40 | println("📋 hooks size: ${options.hooks?.size}") 41 | 42 | val client = ClaudeCodeSdkClient(options) 43 | 44 | try { 45 | client.connect() 46 | client.query("读取 README.md 前3行") 47 | 48 | client.receiveResponse().collect { message -> 49 | if (message is ResultMessage) { 50 | println("✅ 完成") 51 | } 52 | } 53 | 54 | println("Hook 调用次数: ${hookCalls.size}") 55 | hookCalls.forEach { (id, input) -> 56 | println(" toolUseId: $id, input: ${input.keys}") 57 | } 58 | 59 | // 验证 hook 被调用且有 toolUseId 60 | assertTrue(hookCalls.isNotEmpty(), "Hook 应该被调用") 61 | assertTrue(hookCalls.any { it.first != null }, "至少一个调用应该有 toolUseId") 62 | 63 | } finally { 64 | client.disconnect() 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "claude-code-plus-frontend", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "npm run proto:generate && vite", 7 | "build": "npm run proto:generate && vite build", 8 | "build:dev": "npm run proto:generate && vite build --mode development", 9 | "preview": "vite preview", 10 | "lint": "eslint src --ext .ts,.vue", 11 | "lint:fix": "eslint src --ext .ts,.vue --fix", 12 | "type-check": "vue-tsc --noEmit", 13 | "proto:generate": "npx buf generate ../ai-agent-proto/src/main/proto", 14 | "test": "vitest", 15 | "test:run": "vitest run", 16 | "test:coverage": "vitest run --coverage" 17 | }, 18 | "dependencies": { 19 | "@bufbuild/protobuf": "^2.2.3", 20 | "@element-plus/icons-vue": "^2.3.2", 21 | "@tiptap/extension-image": "^3.11.1", 22 | "@tiptap/extension-link": "^3.11.1", 23 | "@tiptap/extension-placeholder": "^3.11.1", 24 | "@tiptap/pm": "^3.11.1", 25 | "@tiptap/starter-kit": "^3.11.1", 26 | "@tiptap/vue-3": "^3.11.1", 27 | "@types/diff": "^7.0.2", 28 | "buffer": "^6.0.3", 29 | "denque": "^2.1.0", 30 | "diff": "^8.0.2", 31 | "element-plus": "^2.11.7", 32 | "github-markdown-css": "^5.8.1", 33 | "highlight.js": "^11.11.1", 34 | "markdown-it": "^14.1.0", 35 | "pinia": "^2.1.7", 36 | "rsocket-core": "^1.0.0-alpha.3", 37 | "rsocket-websocket-client": "^1.0.0-alpha.3", 38 | "shiki": "^3.14.0", 39 | "vue": "^3.4.0", 40 | "vue-i18n": "^11.2.2", 41 | "vue-virtual-scroller": "^2.0.0-beta.8", 42 | "vuedraggable": "^4.1.0" 43 | }, 44 | "devDependencies": { 45 | "@bufbuild/buf": "^1.48.0", 46 | "@bufbuild/protoc-gen-es": "^2.2.3", 47 | "@eslint/js": "^9.0.0", 48 | "@typescript-eslint/eslint-plugin": "^8.0.0", 49 | "@typescript-eslint/parser": "^8.0.0", 50 | "@vitejs/plugin-vue": "^5.0.0", 51 | "@vue/eslint-config-typescript": "^14.0.0", 52 | "@vue/test-utils": "^2.4.6", 53 | "eslint": "^9.0.0", 54 | "eslint-plugin-vue": "^9.17.0", 55 | "happy-dom": "^20.0.11", 56 | "jsdom": "^27.3.0", 57 | "terser": "^5.37.0", 58 | "typescript": "5.4.5", 59 | "unplugin-auto-import": "^20.2.0", 60 | "unplugin-vue-components": "^30.0.0", 61 | "vite": "^5.0.0", 62 | "vite-plugin-compression": "^0.5.1", 63 | "vitest": "^4.0.16", 64 | "vue-tsc": "^1.8.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /jetbrains-plugin/src/main/kotlin/com/asakii/plugin/mcp/tools/terminal/TerminalKillTool.kt: -------------------------------------------------------------------------------- 1 | package com.asakii.plugin.mcp.tools.terminal 2 | 3 | import mu.KotlinLogging 4 | 5 | private val logger = KotlinLogging.logger {} 6 | 7 | /** 8 | * TerminalKill 工具 - 终止终端会话(支持批量) 9 | */ 10 | class TerminalKillTool(private val sessionManager: TerminalSessionManager) { 11 | 12 | /** 13 | * 终止终端会话 14 | * 15 | * @param arguments 参数: 16 | * - session_ids: List - 要终止的会话 ID 列表 17 | * - all: Boolean - 是否终止所有会话 18 | * 至少提供 session_ids 或 all 中的一个 19 | */ 20 | @Suppress("UNCHECKED_CAST") 21 | fun execute(arguments: Map): Map { 22 | val sessionIds = arguments["session_ids"] as? List 23 | val killAll = arguments["all"] as? Boolean ?: false 24 | 25 | // 收集要删除的会话 ID 26 | val idsToKill = when { 27 | killAll -> sessionManager.getAllSessions().map { it.id } 28 | sessionIds != null -> sessionIds 29 | else -> return mapOf( 30 | "success" to false, 31 | "error" to "Missing required parameter: session_ids or all" 32 | ) 33 | } 34 | 35 | if (idsToKill.isEmpty()) { 36 | return mapOf( 37 | "success" to true, 38 | "message" to "No sessions to terminate", 39 | "killed" to emptyList(), 40 | "failed" to emptyList() 41 | ) 42 | } 43 | 44 | logger.info { "Killing ${idsToKill.size} terminal session(s): $idsToKill" } 45 | 46 | // 执行删除 47 | val killed = mutableListOf() 48 | val failed = mutableListOf() 49 | 50 | for (id in idsToKill) { 51 | if (sessionManager.killSession(id)) { 52 | killed.add(id) 53 | } else { 54 | failed.add(id) 55 | } 56 | } 57 | 58 | return buildMap { 59 | put("success", failed.isEmpty()) 60 | put("killed", killed) 61 | put("failed", failed) 62 | put("message", when { 63 | failed.isEmpty() -> "All ${killed.size} session(s) terminated successfully" 64 | killed.isEmpty() -> "Failed to terminate all ${failed.size} session(s)" 65 | else -> "Terminated ${killed.size} session(s), failed ${failed.size}" 66 | }) 67 | } 68 | } 69 | } 70 | --------------------------------------------------------------------------------