使用 .NET 和 Avalonia 构建高性能 Agent 桌面客户端 – 239573049

使用 .NET 和 Avalonia 构建高性能 Agent 桌面客户端 – 239573049


Use .NET 11 RC2, Avalonia and AtomUI to build TKCode, introduce the official login access of native Claude Code and Codex, independent Agent Host architecture, self-developed StreamMark streaming Markdown rendering, and the measurement results of NativeAOT first frame and memory CPU.

This article is based on the project source code, sample interface and test records, and is organized with the assistance of AI.

A programming agent client must simultaneously handle model streaming output, tool invocation, command execution, file differences, and increasingly longer sessions. No matter how fast the model responds, if the interface is constantly rearranged, scrolling is stuck, or the CPU continues to be occupied when idle, the user experience will still be compromised.

We are building TKCode with .NET, Avalonia and AtomUI, and developed an independent streaming Markdown component library StreamMark around these issues. TKCode provides unified desktop interaction with native Claude Code, Codex and built-in Agent; StreamMark is responsible for turning continuously arriving text into a native interface that can be read, selected, copied and reviewed.

This article introduces their architecture and implementation, demonstrates complex Markdown and product functions, and gives measured startup, memory and CPU data. Currently, TKCode is still in the early development stage, and this article corresponds to the local working tree on October 19, 2026.

使用 .NET 和 Avalonia 构建高性能 Agent 桌面客户端 – 239573049

Figure 1: Final response, form, code, and file change entry shown in the same session. The product screenshots in this article are all rendered by real Avalonia components through Headless and Skia. The content comes from preset sample projects or test events and does not represent a real paid model call.

Log in to the local Claude Code and Codex with your own subscription

Many users have purchased Claude or ChatGPT subscriptions and want to continue using their existing coding tools in their desktop workbench. TKCode’s external runtime access method is: start the official CLI installed on the user’s local machine. The CLI handles model access and existing login status. TKCode receives the events it outputs and provides an interface.

These are two clearly separate paths from the built-in Agent:

Usage Who executes the Agent loop How to authenticate and bill
Built-in Agent TKCode Agent Host Configure model manufacturer API Key or compatible services, and bill according to corresponding service rules
Claude Agent Native Claude Code Users log in to their accounts in the official CLI, and the actual authentication method determines whether to use subscription or API billing.
Codex local machine codex app-server ChatGPT login or API Key is managed by Codex and runs according to the corresponding account and workspace permissions.

Login remains in the official tool

First install the CLI according to the official instructions, and then complete the login and status check in the terminal. Claude Code CLI Reference · Codex Certification

# Claude Code
claude auth login
claude auth status

# Codex
codex login
codex login status

TKCode discovers the runtime in the background and reads its capabilities and model directories. The user then selects the corresponding runtime in the model selector, using it in the same set of input boxes, session lists, and workbench. Claude Code passed claude -p stream-json and control channel access; Codex passes codex app-server JSON-RPC access. TKCode does not read or save the CLI’s subscription token file itself.

Codex’s official certification document clearly distinguishes between ChatGPT subscription login and pay-as-you-go API Key; the App Server document provides the session, streaming events and approval interfaces required to build a custom client. The authentication path of the native Codex is used here, which does not mean that all ChatGPT functions or any model API can consume the same subscription credit. OpenAI official certification document · Codex App Server

Official access has boundaries and should not promise that the account will never be blocked.

Anthropic’s current terms allow running unmodified Claude Code in products that comply with the Commercial Terms and Conditions, and allow end users to log into the official binary with their own subscription. At the same time, the terms prohibit third parties from collecting, saving or transferring Claude.ai credentials, and also restrict third parties from using their own application login portals to forward subscription requests on behalf of users. TKCode is designed to call native official tools, retain its authentication mechanism, and allow users to use their own accounts through the official process; deployment and distribution must still comply with applicable terms. Claude Code Legal and Compliance Instructions

Therefore, what we can explain is the specific access methods and credential boundaries, and we cannot promise that any account will “definitely not be banned.” Subscription quotas, organizational policies, usage behavior and vendor rules still apply; subscription does not mean unlimited calls. If it is set in the Claude Code environment ANTHROPIC_API_KEYit may also be changed to API billing, and the certification status should be checked before use. Instructions for use of Claude Pro and Max

Let the workbench appear first and then load the Agent capability.

TKCode currently uses .NET 11 RC2, Avalonia 12.1.3 and AtomUI 6.2.5. The desktop, Agent Host and terminal Host are all released according to NativeAOT. Window, session transcription, file preview and terminal use Avalonia’s native controls and drawing pipeline, and Markdown display does not rely on WebView.

The first screen only needs to allow users to see items, input boxes and basic operations. Model catalogs, history sessions, and external runtime probing can be done later. We deleted the original startup loading page that was displayed for at least 1.8 seconds, and placed the postscript above the creation session area as static copy; Host initialization was queued into a lower-priority interface scheduling queue, and window layout and rendering were prioritized.

TKCode creates conversation first screen and static afterwords

Figure 2: The creation session interface is displayed directly. There is no need for a carousel timer for copywriting, and there is no delay in entering the workbench while waiting for a sentence to finish playing.

The first frame after packaging is about 383 milliseconds

On Apple M5, 32 GiB RAM, macOS 26.6.2, for current osx-arm64 After AOT is released and packaged .app Take 10 independent process measurements:

scene From starting the process to the first frame completion signal
1st run using fresh configuration directory 385.1 ms
The median of the next 9 runs 382.8 ms
The range of the next 9 runs 368.8–388.0 ms

The first frame here is Headless and Skia have completed rendering, and confirmed that the postscript and input box are visibleexcluding PNG encoding time. The measurement does not clear the system cache, does not include Finder startup, Gatekeeper first check and macOS window synthesis, nor does it mean that the model and all historical sessions have been loaded.

This set of data verifies that the first frame is no longer subject to the minimum display time of the loading page. The currently released desktop and two hosts are arm64 native executable files, as shown in the release log IL#### Zero warnings, release version headless smoke check passed. The measured values ​​for 10 starts are 385.1, 380.5, 388.0, 384.1, 383.2, 368.8, 379.9, 375.2, 382.8 and 383.2 ms.

Use an independent Agent Host to keep interface responsibilities clear

TKCode separates the interface and Agent execution into independent processes. Desktop handles input, approval, display and workbench; Agent Host manages sessions, tools and runtime; the terminal also has a PTY Host, which is responsible for the life cycle of the terminal session.

The dependency direction of the built-in Agent remains unidirectional:

TKCode.Desktop
    │ JSON-RPC 2.0 / stdio
    ▼
TKCode.AgentHost
    ├── TKCode.Agent.Coding   编码会话、工具、系统提示、持久化
    │       └── TKCode.Agent  Agent 循环、审批与事件
    │               └── TKCode.Ai  统一消息与模型协议适配
    └── TKCode.Runtimes
            ├── Claude Code
            ├── Codex app-server
            └── ACP Agent

The bottom three built-in Agent projects do not reference the UI. The same session and tool logic can be tested outside the window, and protocol errors do not require interface operations to reproduce. Process boundaries make responsibilities and fault handling clearer, but it does not mean that the total memory must be lower. Related processes must still be added up when counting resources.

Translate different runtimes into the same set of events

For desktop, key events are text increment, thought increment, tool start and end, approval request, usage, and round end. The protocol differences between Claude, Codex and built-in Agent are left in the runtime adaptation layer, and UI consumption is unified. SessionEvent。

The external runtime is still responsible for its own tool loop and context management, TKCode does not treat them as a pure text interface. The adaptation layer saves the native session ID, maps interruption and recovery capabilities, and connects operations that require user decision-making to desktop approval. Judging completion is also based on the termination event of the corresponding round, rather than “if there is no text for a few seconds, it is over.”

Use Mermaid in TKCode to demonstrate the Agent runtime architecture

Figure 3: This architecture diagram itself is a Mermaid Markdown preview in the TKCode workbench, rendered by the native drawing pipeline.

Session records and protocol data also affect stability

Sessions are saved locally as append-only JSONL records. System prompts and toolsets are frozen at the beginning of the session, and subsequent changes are expressed through append messages; context compression only occurs between model requests, and old records are still available for historical review.

The adapter must also retain data that the manufacturer requires to be played back unchanged, such as thinking signatures and reasoning items. If these fields are discarded in order to unify the interface, the next round of requests may fail. When an error occurs in the flow, we end it through an error event and retain part of the content that has been received to avoid clearing the entire reply in the event of a round of network failure.

After high-frequency increments enter the Desktop, they are first queued and then merged and scheduled to the UI thread, which reduces the overhead of delivering interface tasks separately for each token. Such optimizations must preserve the order of events so that tool calls and results can correspond correctly.

Developing StreamMark for streaming conversations

Static Markdown readers usually get a complete document. The Agent output may stop at half a word, an unclosed code fence, a table row, or a formula at any time; the previous content may already contain tens of thousands of words.

The StreamMark we developed focuses on handling this continuous appending scenario. It is built on existing components such as Markdig and Avalonia. Its core work is the streaming document model, incremental parsing and typesetting, virtualized sessions and native interaction; syntax highlighting, formulas and charts integrate TextMate, RaTeX and Mermaid rendering capabilities respectively. StreamMark source code

StreamMark displays a mix of tabular C# highlighting and LaTeX formulas within the product

Figure 4: The same Markdown mixes Chinese styles, tables, C# code highlighting and attention formulas, using a dark theme consistent with the product.

Freeze stable prefixes and only process changing tails

StreamMark’s streaming processing can be summarized as:

文本增量 → 节奏控制 → 只追加源码缓冲 → 后台解析
                                      │
                       已冻结前缀共享 + 可变尾部
                                      ↓
                       不可变快照 → 差异 → 更新块视图

The parser scans for safe freezing boundaries, and fragments that are already stable are parsed once within the same document generation and shared with subsequent snapshots. The new content mainly affects the tail; unclosed code blocks can also take the fast path and avoid calling the full parser for every append.

Parsing tasks are merged serially in the background: if a new text is received before a parsing is completed, the latest status will be processed subsequently, reducing duplication of work on intermediate versions. The interface layer reads the latest snapshot at most once per frame, and then applies insertion, update, and deletion based on the stable block key.

A full parse will still be performed after the stream ends, correcting semantics such as reference definitions, footnotes, and lists that may span fragments. Incremental strategies serve streaming response speed, but the end result must still be consistent with the meaning of the complete document.

Incremental processing also extends to typesetting and drawing

Simply reducing parsing is not enough. The text block saves the layout and visual objects by rows, and the appended content is retyped from the vicinity of the difference, and the changed rows are re-recorded. Code highlighting also caches syntax state per line, avoiding recoloring the entire code block for each token.

Long session use TranscriptPanel Virtualize, expand the Markdown top-level block and host content into lines, and only instantiate the lines near the viewport. Selection and copying use a coordinate model that is independent of the control instance, so the semantics of cross-block selection and copying are preserved after content scrolls out of the viewport. single document MarkdownView It is not virtualized per se, and the capabilities of the session container cannot be mistaken as being natively available to all document views. Historical text and message models still occupy data memory, and virtualization mainly reduces control, layout and drawing costs.

The formulas are typeset natively by RaTeX; Mermaid renders in the background after the code block is closed, and then performs vector drawing through SVG and Skia. Heavy rendering does not require a restart on each text increment arrival.

Rich display should be combined with review operations

Markdown in programming tasks also contains differences, task lists, and instructional details. Users need to see what has changed, what the tool has done, and where the verification has progressed.

Code diff task list and expansion instructions in Markdown

Figure 5: Diff’s addition and deletion background, inline difference highlighting, task list and details expansion content. The task status in the screenshot is a sample display.

TKCode combines streaming replies, tool activity, and file changes into the same session. Markdown diff is suitable for explaining modifications, and the file change panel provides a review entry to prevent users from only seeing the sentence “Completed” but not finding the specific changes.

Reduce memory usage and stop idle interfaces

Resource optimization requires a clear view of resident objects, pictures, fonts and timers. NativeAOT is part of the deployment and startup solution and cannot replace these tasks.

Don’t warm up all syntax on startup

TKCode’s TextMate highlighter loads on demand by language and current theme, avoiding the need to build full syntax objects ahead of time for languages ​​that haven’t been opened yet. The code is displayed first, and then highlighted by background tasks. The first screen does not pay the full cost for capabilities that may be used in the future.

The Markdown image is decoded according to the display size and the reduced result is saved to the disk cache. In the default compression mode, a density of one decoded pixel per logical pixel is used; for the same display area, 2x Retina’s pixel storage can be reduced to a quarter, but this does not mean that the entire application memory is also reduced by the same proportion. When users view the original image, they still need to bear the memory cost of the original image.

Hidden animations must also be stopped

An invisible but still running spinning animation, possibly constantly requesting redraws. TKCode’s lattice loading control checks the effective visibility of itself and its ancestors: stopping the timer when leaving the visual tree or being hidden. The postscript on the homepage is displayed statically, and additional carousel wake-ups are removed.

The goal is to make the window quiet when there is no input, network deltas, and active tasks. Low idle CPU and CPU while streaming output are two different metrics; computing resources are still required during parsing, chart rendering, and actual tool execution.

Logged memory and idle CPU data

A set of macOS arm64 historical benchmarks saved in the warehouse are as follows, recorded on October 17, 2026, and the version is marked as a9d6070. The benchmark uses isolation configuration and simulation model, waits for the scene to stabilize before active GC, and then counts the footprint of the desktop process; MiB in the table is converted to 1024² bytes.

scene stable footprint historical peak GC heap Let the CPU rest
idle window 139.0 MiB 266.6 MiB 21.2 MiB 0.0%
Markdown loaded for the first time 204.6 MiB 312.3 MiB 21.9 MiB 0.0%
Markdown cache reuse 159.9 MiB 267.5 MiB 23.9 MiB 0.0%
After the conversation 163.9 MiB 285.6 MiB 28.0 MiB 0.0%

These are previously built scenario records and cannot replace the same-condition memory retest for the current .NET 11 RC2 version. they reflectdesktop processdoes not include Agent Host, PTY Host, Claude Code, Codex, or tool subprocesses; simulated conversations do not represent real inference loads.

CPU is calculated by dividing the process CPU time increment by the duration of the idle window. The default observation is 10 seconds, and then rounded to one decimal place.0.0% Indicates that the results within this window are rounded to 0.0% and does not mean that there will never be CPU activity. The stable value and peak value after active GC in the memory table also need to be looked at together. You cannot just pick the minimum value as a commitment for all usage scenarios. Original records from the project artifacts/mem-bench.txtthe measurement logic is located at bench/TKCode.MemoryHarness/Program.cs。

This set of results shows that the native interface can contain Markdown, code, and image capabilities while controlling idle occupancy to a smaller range. To further evaluate “extremely low” or better than a certain client, comparative testing after unifying the workload and complete process statistics is required.

Let Agent become a desktop workbench that can be used daily

TKCode organizes tasks around projects and sessions. When creating a session, select the working directory, model and mode; after entering the session, the input area is docked to the bottom, leaving space for output and operation records.@ file reference,/ Commands and skills, and image attachments keep contextual input focused.

Conversational code preview in the workbench alongside the project file tree

Figure 6: Session, code preview, and file tree shown side by side. Long codes can be scrolled horizontally, and the workbench can be expanded to occupy the main area.

Built-in file, search, command and task tools turn operations into structured events. MCP access provides tools, resources, and prompt word extensions; external runtime capabilities and permissions are still mapped according to their respective protocols, and all modes cannot be assumed to be exactly the same.

File diff command output and subtasks in tool activities

Figure 7: Simulate event-driven real tool activity interface, showing file differences, command output, subtask statistics and background notifications. The number of tokens and test output in the figure are interface test data, not performance measurements.

The command panel provides centralized access to create new conversations, open settings, switch themes, and workbench; Chinese, English, and light and dark themes share the same set of components and semantics. The unified design token also allows formulas, tables and charts to follow the product theme, reducing visual fragmentation between different rendering areas.

TKCode command panel

Figure 8: The command panel reduces the menu hierarchy with search and keyboard navigation.

NativeAOT release and reproducible verification

TKCode constrains AOT compatibility from project boundaries: JSON is generated using the source JsonSerializerContextavoid relying on runtime reflection and dynamic code generation, XAML and binding are handled at build time. The three types of executable programs are released separately. The stdout of the Host is specially used to transmit RPC, and the log is sent to stderr to avoid mixing the log text into the protocol stream.

Successful release does not mean that the interface verification is completed. We run headless smoke checks on post-release binaries, covering model loading, simulation conversations, Markdown, tools, terminals, and first-frame screenshots. This can identify problems with missing resources when the development build is normal, trimmed, or after AOT.

The current macOS packaging and first frame measurement can be reproduced as:

bash tools/package_macos_app.sh osx-arm64 artifacts/startup-bench
python3 tools/startup-bench.py artifacts/startup-bench/TKCode.app

artifacts/startup-bench/TKCode.app/Contents/MacOS/TKCode \
  --smoke --out=artifacts/startup-bench/smoke-frame.png

Article screenshots can also be regenerated from the sample project without operating a real desktop or using a real account request:

mkdir -p docs/articles/images/tkcode
TKCODE_ARTICLE_SCREENSHOTS=1 bash tools/readme-screenshots.sh docs/articles/images/tkcode

TKCODE_SNAPSHOT_DIR="$PWD/docs/articles/images/tkcode" \
  dotnet test tests/TKCode.Desktop.Tests --no-build \
  --filter 'FullyQualifiedName~AgentToolsSnapshotTests'

The TKCode project and the StreamMark project maintain desktop Agent workflow and native streaming Markdown capabilities respectively. Next, you need to continue to verify with long sessions, large files, real CLI and multi-platform load: not only look at the first frame, but also look at the response, memory and complete process cost after continuous operation.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *