Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
/**
* Shared LLM HTTP client supporting Ollama, OpenAI-compatible, and Anthropic (including Vertex AI) APIs.
*/
class LlmClient {
public class LlmClient {

private static final String DEFAULT_OLLAMA_URL = "http://localhost:11434";
private static final String DEFAULT_ANTHROPIC_URL = "https://api.anthropic.com";
Expand All @@ -58,39 +58,39 @@ class LlmClient {
"claude-opus-4-5", "claude-opus-4-5@20251101",
"claude-haiku-4-5", "claude-haiku-4-5@20251001");

enum ApiType {
public enum ApiType {
ollama,
openai,
anthropic
}

// -- Unified abstractions for tool-calling across API formats --

record ToolDef(String name, String description, JsonObject parameters) {
public record ToolDef(String name, String description, JsonObject parameters) {
}

record ToolCall(String id, String name, JsonObject arguments) {
public record ToolCall(String id, String name, JsonObject arguments) {
}

record ToolResult(String toolCallId, String content) {
public record ToolResult(String toolCallId, String content) {
}

record Message(String role, String content, List<ToolCall> toolCalls, List<ToolResult> toolResults) {
public record Message(String role, String content, List<ToolCall> toolCalls, List<ToolResult> toolResults) {

static Message user(String text) {
public static Message user(String text) {
return new Message("user", text, null, null);
}

static Message assistantWithToolCalls(String text, List<ToolCall> calls) {
public static Message assistantWithToolCalls(String text, List<ToolCall> calls) {
return new Message("assistant", text, calls, null);
}

static Message toolResults(List<ToolResult> results) {
public static Message toolResults(List<ToolResult> results) {
return new Message("tool", null, null, results);
}
}

record ChatResponse(String text, List<ToolCall> toolCalls, String stopReason, boolean streamed) {
public record ChatResponse(String text, List<ToolCall> toolCalls, String stopReason, boolean streamed) {
}

// -- Configuration --
Expand All @@ -104,7 +104,23 @@ record ChatResponse(String text, List<ToolCall> toolCalls, String stopReason, bo
boolean stream;
int maxTokens;
boolean verbose;
Printer printer;
Printer printer = new Printer() {
@Override
public void println() {
}

@Override
public void println(String line) {
}

@Override
public void print(String output) {
}

@Override
public void printf(String format, Object... args) {
}
};

private final HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(CONNECT_TIMEOUT_SECONDS))
Expand All @@ -116,63 +132,63 @@ record ChatResponse(String text, List<ToolCall> toolCalls, String stopReason, bo

// -- Builder --

static LlmClient create() {
public static LlmClient create() {
return new LlmClient();
}

LlmClient withApiType(ApiType apiType) {
public LlmClient withApiType(ApiType apiType) {
this.apiType = apiType;
return this;
}

LlmClient withUrl(String url) {
public LlmClient withUrl(String url) {
this.url = url;
return this;
}

LlmClient withApiKey(String apiKey) {
public LlmClient withApiKey(String apiKey) {
this.apiKey = apiKey;
return this;
}

LlmClient withModel(String model) {
public LlmClient withModel(String model) {
this.model = model;
return this;
}

LlmClient withTimeout(int timeout) {
public LlmClient withTimeout(int timeout) {
this.timeout = timeout;
return this;
}

LlmClient withTemperature(double temperature) {
public LlmClient withTemperature(double temperature) {
this.temperature = temperature;
return this;
}

LlmClient withStream(boolean stream) {
public LlmClient withStream(boolean stream) {
this.stream = stream;
return this;
}

LlmClient withMaxTokens(int maxTokens) {
public LlmClient withMaxTokens(int maxTokens) {
this.maxTokens = maxTokens;
return this;
}

LlmClient withVerbose(boolean verbose) {
public LlmClient withVerbose(boolean verbose) {
this.verbose = verbose;
return this;
}

LlmClient withPrinter(Printer printer) {
public LlmClient withPrinter(Printer printer) {
this.printer = printer;
return this;
}

// -- Auto-detection --

boolean detectEndpoint() {
public boolean detectEndpoint() {
boolean found;
if (tryExplicitUrl()) {
found = true;
Expand Down Expand Up @@ -232,7 +248,7 @@ String generate(String systemPrompt, String userPrompt) {

// -- Chat with tools (for ask) --

ChatResponse chatWithTools(String systemPrompt, List<Message> messages, List<ToolDef> tools) {
public ChatResponse chatWithTools(String systemPrompt, List<Message> messages, List<ToolDef> tools) {
return switch (apiType) {
case ollama -> chatOllamaFormat(systemPrompt, messages, tools);
case openai -> chatOpenAiFormat(systemPrompt, messages, tools);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,12 @@ enum Action {
SETUP_AI,
MCP_INFO,
MCP_LOG,
AI_LOG,
SHELL
}

private static final int[] GROUP_SIZES = { 5, 4, 5 };
private static final int MCP_GROUP_SIZE = 3;
private static final int MCP_GROUP_SIZE = 4;
private static final int SHELL_GROUP_SIZE = 1;

private final Supplier<Set<String>> runningNames;
Expand Down Expand Up @@ -148,6 +149,7 @@ enum Action {
private String selectedFolder;

private final McpLogPopup mcpLogPopup = new McpLogPopup();
private final AiLogPopup aiLogPopup = new AiLogPopup();

private final DoctorPopup doctorPopup = new DoctorPopup();
private final SendMessagePopup sendMessagePopup = new SendMessagePopup();
Expand Down Expand Up @@ -218,6 +220,10 @@ void setMcpEnabled(
mcpLogPopup.setActivityLog(activityLog);
}

void setAiActivityLog(Supplier<List<AiPanel.LogEntry>> activityLog) {
aiLogPopup.setActivityLog(activityLog);
}

private int visualActionCount() {
int total = 0;
for (int gs : GROUP_SIZES) {
Expand Down Expand Up @@ -273,7 +279,7 @@ private List<Action> buildVisualActionList() {
Action.SHOW_KEYSTROKES));
if (mcpEnabled) {
flat.add(null);
flat.addAll(List.of(Action.SETUP_AI, Action.MCP_INFO, Action.MCP_LOG));
flat.addAll(List.of(Action.SETUP_AI, Action.MCP_INFO, Action.MCP_LOG, Action.AI_LOG));
}
flat.add(null);
flat.add(Action.SHELL);
Expand All @@ -298,7 +304,7 @@ boolean isVisible() {
return showActionsMenu || showExampleBrowser || showFolderInput || runOptionsForm.isVisible()
|| showDocPicker || showDocViewer
|| showInfraBrowser || showInfraPortDialog
|| mcpLogPopup.isVisible() || doctorPopup.isVisible()
|| mcpLogPopup.isVisible() || aiLogPopup.isVisible() || doctorPopup.isVisible()
|| sendMessagePopup.isVisible() || stopAllPopup.isVisible() || captionOverlay.isInlineMode();
}

Expand Down Expand Up @@ -366,6 +372,7 @@ List<String> getActionLabels() {
labels.add("Setup AI...");
labels.add("MCP Info");
labels.add("MCP Log");
labels.add("AI Log");
}
labels.add("───");
labels.add("Shell");
Expand All @@ -387,6 +394,7 @@ void close() {
showInfraBrowser = false;
showInfraPortDialog = false;
mcpLogPopup.close();
aiLogPopup.close();
doctorPopup.close();
sendMessagePopup.close();
stopAllPopup.close();
Expand Down Expand Up @@ -419,6 +427,9 @@ boolean handleKeyEvent(KeyEvent ke) {
if (mcpLogPopup.handleKeyEvent(ke)) {
return true;
}
if (aiLogPopup.handleKeyEvent(ke)) {
return true;
}
if (showDocViewer) {
if (ke.isCancel()) {
showDocViewer = false;
Expand Down Expand Up @@ -608,6 +619,9 @@ boolean handleKeyEvent(KeyEvent ke) {
} else if (action == Action.MCP_LOG) {
showActionsMenu = false;
openMcpLog();
} else if (action == Action.AI_LOG) {
showActionsMenu = false;
openAiLog();
} else if (action == Action.SEND_MESSAGE) {
showActionsMenu = false;
openSendMessage();
Expand Down Expand Up @@ -664,6 +678,9 @@ void render(Frame frame, Rect area) {
if (mcpLogPopup.isVisible()) {
mcpLogPopup.render(frame, area);
}
if (aiLogPopup.isVisible()) {
aiLogPopup.render(frame, area);
}
if (doctorPopup.isVisible()) {
doctorPopup.render(frame, area);
}
Expand Down Expand Up @@ -695,6 +712,10 @@ void renderFooter(List<Span> spans) {
doctorPopup.renderFooter(spans);
return;
}
if (aiLogPopup.isVisible()) {
aiLogPopup.renderFooter(spans);
return;
}
if (mcpLogPopup.isVisible()) {
mcpLogPopup.renderFooter(spans);
return;
Expand Down Expand Up @@ -809,6 +830,7 @@ private void renderActionsMenu(Frame frame, Rect area) {
items.add(ListItem.from(" 🧠 Setup AI..."));
items.add(ListItem.from(" 🤖 MCP Info"));
items.add(ListItem.from(" 📋 MCP Log"));
items.add(ListItem.from(" 💬 AI Log"));
}
// Group 5: Shell
items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
Expand Down Expand Up @@ -1245,6 +1267,10 @@ private void openMcpLog() {
mcpLogPopup.open();
}

private void openAiLog() {
aiLogPopup.open();
}

// ---- Folder Input ----

private void openFolderInput() {
Expand Down Expand Up @@ -2171,6 +2197,7 @@ boolean executeActionByName(String name) {
case SETUP_AI -> openSetupAI();
case MCP_INFO -> openMcpInfo();
case MCP_LOG -> openMcpLog();
case AI_LOG -> openAiLog();
default -> {
return false;
}
Expand Down
Loading