Plain 渲染器:纯 println 模式,等价 phase-15 行为,无折叠、无状态栏。 同时充当 inline / lanterna 两套实现的回退基线——任何高级特性都退化成普通文本。
| 24 | * <p>同时充当 inline / lanterna 两套实现的回退基线——任何高级特性都退化成普通文本。 |
| 25 | */ |
| 26 | public final class PlainRenderer implements Renderer { |
| 27 | |
| 28 | private static final ObjectMapper JSON = new ObjectMapper(); |
| 29 | |
| 30 | private final PrintStream out; |
| 31 | private final BufferedReader in; |
| 32 | |
| 33 | public PlainRenderer() { |
| 34 | this(System.out, new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))); |
| 35 | } |
| 36 | |
| 37 | PlainRenderer(PrintStream out, BufferedReader in) { |
| 38 | this.out = out; |
| 39 | this.in = in; |
| 40 | } |
| 41 | |
| 42 | @Override |
| 43 | public void start() { |
| 44 | // no-op |
| 45 | } |
| 46 | |
| 47 | @Override |
| 48 | public void close() { |
| 49 | // no-op:不接管 System.out / System.in,启动者自己管生命周期 |
| 50 | } |
| 51 | |
| 52 | @Override |
| 53 | public PrintStream stream() { |
| 54 | return out; |
| 55 | } |
| 56 | |
| 57 | @Override |
| 58 | public void appendToolCalls(List<LlmClient.ToolCall> toolCalls) { |
| 59 | if (toolCalls == null || toolCalls.isEmpty()) { |
| 60 | return; |
| 61 | } |
| 62 | Map<String, List<LlmClient.ToolCall>> grouped = new LinkedHashMap<>(); |
| 63 | for (LlmClient.ToolCall tc : toolCalls) { |
| 64 | grouped.computeIfAbsent(tc.function().name(), k -> new ArrayList<>()).add(tc); |
| 65 | } |
| 66 | for (var group : grouped.entrySet()) { |
| 67 | String toolName = group.getKey(); |
| 68 | List<LlmClient.ToolCall> calls = group.getValue(); |
| 69 | out.println(AnsiStyle.subtle(" " + toolLabel(toolName, calls.size()))); |
| 70 | for (LlmClient.ToolCall tc : calls) { |
| 71 | String detail = extractKeyParam(toolName, tc.function().arguments()); |
| 72 | if (!detail.isEmpty()) { |
| 73 | out.println(AnsiStyle.subtle(" └ " + detail)); |
| 74 | } |
| 75 | } |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | @Override |
| 80 | public void appendDiff(String filePath, String before, String after) { |
| 81 | out.println(); |
| 82 | out.println(AnsiStyle.heading("📝 " + (filePath == null ? "(unnamed)" : filePath))); |
| 83 | if (before == null && after != null) { |
nothing calls this directly
no outgoing calls
no test coverage detected