AtomCode的Daemon 模式完全指南:使用方式与通信协议详解

摘要:AtomCode 是一款强大的 AI 编码智能体,除了交互式 TUI 和 CLI 无头模式外,它还提供 Daemon(守护进程)模式——以 HTTP + SSE API 的形式将 AtomCode 的全部能力暴露给外部客户端。本文将全面介绍 Daemon 模式的启动方式、所有 API 端点、通信协议细节,并提供多种语言的客户端代码示例。


前言:为什么你需要 Daemon 模式?

AtomCode Daemon 模式的核心价值在于:将 AI 编码智能体转化为一个可编程的 HTTP 服务。这意味着你可以像调用普通 REST API 一样,在任何编程语言、任何平台、任何场景中驱动 AtomCode。

🚀 它能做什么?

1. 无人值守的 24/7 编码助手

Daemon 启动后便常驻后台,不限时长、不限对话次数。你可以在工作时打开一个终端跑着 Daemon,随时用脚本或 Web 页面发请求——它永远在待命。

# 启动一次,永久在线
atomcode daemon &

# 随时随地调用
curl -X POST http://127.0.0.1:13456/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"帮我 review 代码"}'

2. GitHub CI/CD 自动化流水线

将 Daemon 接入 CI/CD 流程(GitHub Actions / GitLab CI / Jenkins),实现提交即审查、合入即测试:

场景触发时机自动执行任务
PR 代码审查pull_request 事件读取 diff → 审查代码风格、安全性、性能 → 自动评论
自动化修复push 到特定分支运行 lint → 自动修复 → 提交修复 commit
单元测试生成合入前分析变更代码 → 生成缺失的单元测试 → 追加到 PR
文档同步release 事件分析 API 变更 → 自动更新 README/CHANGELOG
依赖审计定时 (cron)扫描 CVE 漏洞 → 自动升级 → 生成审计报告
# .github/workflows/pr-review.yml 示例
on: pull_request
jobs:
  atomcode-review:
    runs-on: self-hosted  # 需要运行 Daemon 的机器
    steps:
      - uses: actions/checkout@v4
      - name: AI Code Review
        run: |
          python3 << 'EOF'
          import requests
          diff = open("/tmp/pr.diff").read()
          resp = requests.post("http://localhost:13456/chat",
              json={"message": f"请审查以下 diff,指出问题:\n{diff}"},
              stream=True)
          for line in resp.iter_lines():
              if line: print(line.decode())
          EOF
        env:
          ATOMCODE_DAEMON_URL: "http://localhost:13456"

3. 自动代码审查后台服务

部署一个 Webhook 服务,监听 Git 平台的审查请求,自动对每次提交/PR 进行深度代码审查。它可以在你睡觉时默默检查代码,早上上班时一份报告已经躺在邮箱里。

┌──────────┐   webhook    ┌─────────────┐   POST /chat   ┌──────────────┐
│  GitLab   │ ──────────►  │  Webhook    │ ──────────────► │  atomcode-   │
│  GitHub   │              │  Server     │                │  daemon      │
│  Gitee    │              │  (Flask)    │ ◄────────────── │              │
└──────────┘              └─────────────┘   SSE 流式结果  └──────────────┘
                                   │
                                   ▼
                            ┌──────────────┐
                            │  自动评论 PR  │
                            │  生成审查报告  │
                            │  触发通知     │
                            └──────────────┘

4. 定时任务 & 批量处理

配合 cron / systemd timer,让 AtomCode 在凌晨自动执行代码质量巡检、生成周报、或批量处理积压的 issue。

# crontab -e
# 每天凌晨 2 点执行代码质量检查
0 2 * * * python3 /opt/scripts/quality_check.py

# 每周一早 9 点生成开发周报
0 9 * * 1 python3 /opt/scripts/weekly_report.py

5. 构建自定义 AI 工作流工具

基于 Daemon API,你可以搭建自己的 AI 工作流平台:

  • Web 管理面板:用 Vue/React 写个网页,让团队通过浏览器使用 AtomCode
  • Slack/钉钉 Bot:在聊天中 @机器人 → 自动调用 Daemon → 返回代码分析结果
  • 多 Agent 编排:同时启动多个 Daemon 实例(不同模型/配置),协调完成复杂任务
  • 内部 API 网关:给 Daemon 套上认证、限流、审计层,暴露为内部微服务

💡 一句话总结

Daemon 模式 = 把你的 AI 编码智能体变成 REST API,任何能发 HTTP 请求的系统都能用它。

无论是搭建无人值守的 CI 审查流水线,还是构建团队共享的 AI 编码平台,Daemon 模式都是 AtomCode 最强大的远程调用接口。
这里顺带推荐下atomcode和taotoken,配合taotoken的套餐,使用起来太给力啦~ 。目前已是我的主要使用神器。

如果你做过 AI 应用或自动化脚本,多半遇到过同一种疲惫:每家厂商一套账号、一套密钥、一套计费口径,想在项目里换个模型,常常不是「改一行参数」这么简单,而是「再集成一遍」。如果你想体验国外厉害的大模型能力,却总是被禁或者服务不稳定。推荐下taotoken,这个是csdn官方推出的产品,速度流畅,稳定可靠。 关键是很便宜,性价比不错。

taotoken尝鲜入口https://taotoken.net/?u=inv_faxm8m42tg11a06f&utm_source=invite

Taotoken 的方向很直白:把「多模型」收敛成「一条统一网关」。它是 CSDN 生态里的 AI 聚合与分发能力载体——面向开发者常见的调用路径,做网关侧的路由与协议适配,让你更少折腾基建,更多时间花在产品与效果上。谐音梗“掏token”,名字起的不错。以后AI时代,token就是食粮,越来越重要了。

在这里插入图片描述



一、概述

1.1 什么是 AtomCode?

AtomCode 是一款开源的 AI 编码智能体,由 AtomGit 开发并开源。它运行在你的终端中,能够自主理解代码库、执行多步任务、调用各种工具(bash、文件读写、代码搜索、Web 访问等),像一个真正的开发者一样完成编码工作。

核心特性:

特性说明
多模型支持对接 100+ 大模型(DeepSeek、OpenAI、Claude、Gemini 等),支持自定义 Provider
自主多步执行自动拆解复杂任务,循环「思考→执行→观察」直到完成
完整工具链bash、文件读写、代码搜索、grepglob、Web 搜索/抓取等 20+ 工具
会话持久化对话历史保存在本地,跨启动可继续上下文
三种运行模式TUI 交互式、CLI 无头式、HTTP Daemon 后台服务
100% 开源Apache 2.0 协议,代码完全公开

官方资源:

资源地址
官方网站 & 文档https://atomcode.atomgit.com
源代码仓库https://atomgit.com/atomgit_atomcode/atomcode
GitHub 镜像https://github.com/atomgit/atomcode
安装方式pip install atomcodenpm install -g @atomgit/atomcode
交流群AtomCode 官方社区

1.2 三种运行模式

模式命令特点
交互式 TUIatomcode终端交互界面,适合日常开发
无头 CLIatomcode -p "任务"单次执行,输出到 stdout,适合 CI/脚本
Daemon 模式atomcode daemon后台 HTTP 服务,供外部客户端驱动

Daemon 模式与传统 Daemon 不同——它不是一个「定时任务守护」,而是一个 HTTP + SSE API 服务器。你可以把它理解为 AtomCode 的「远程过程调用层」:启动后,任何能发 HTTP 请求的客户端(Python 脚本、VS Code 插件、Web 前端、桌面应用)都可以通过 API 与 AtomCode 交互。

它的架构如下:

┌──────────────────────────────────────────────────┐
│                   客户端                          │
│  (Python 脚本 / JS 前端 / IDE 插件 / 桌面应用)    │
└──────────────────┬───────────────────────────────┘
                   │ HTTP REST + SSE (JSON)
                   ▼
┌──────────────────────────────────────────────────┐
│              atomcode-daemon                     │
│           (HTTP 服务层,端口 13456)               │
└──────────────────┬───────────────────────────────┘
                   │ 内部调用
                   ▼
┌──────────────────────────────────────────────────┐
│              atomcode-core                       │
│    (AI 智能体核心: LLM + 工具循环 + 会话管理)     │
└──────────────────────────────────────────────────┘

Daemon 与 CLI 共享同一份配置~/.atomcode/config.toml),登录状态、Provider 配置、会话历史全部通用。


二、启动 Daemon

2.1 基本启动

# 最简单的方式
atomcode daemon

输出类似:

[2026-07-04T10:00:00Z INFO  atomcode_daemon] Listening on http://127.0.0.1:13456
[2026-07-04T10:00:00Z INFO  atomcode_daemon] Idle timeout: 1800s

2.2 带参数启动

# 指定端口和客户端标识
atomcode daemon --port 13456 --client vscode

# 直接调用 daemon 二进制(更多底层选项)
atomcode-daemon --host 127.0.0.1 --port 13456 \
                --client atomcode-air \
                --idle-timeout 3600

2.3 所有启动参数

参数默认值说明
--host HOST127.0.0.1监听地址。非 loopback 地址会触发安全警告
--port PORT13456监听端口
--client NAME客户端身份标识:vscode / atomcode-air / 其他
--idle-timeout SECS1800 (30分钟)空闲超时;0 禁用;最小 60
--no-telemetry禁用遥测

--idle-timeout 也支持环境变量 ATOMCODE_DAEMON_IDLE_TIMEOUT

2.4 验证是否启动成功

# 健康检查
curl http://127.0.0.1:13456/health

# 响应示例
{"status":"ok"}

三、API 端点全面览

所有端点按功能分组如下。

3.1 健康与生命周期

方法路径说明
GET/health健康检查。返回 {"status":"ok"}
POST/shutdown优雅关闭 Daemon(客户端退出时调用)

3.2 会话与项目管理

方法路径说明
GET/sessions列出所有历史会话
POST/sessions创建新会话
GET/sessions/search按关键词搜索历史会话
GET/project当前工作目录的项目状态
POST/cd切换工作目录。请求体:{"path": "/new/path"}
GET/projects列出所有有过会话的项目
GET/projects/:hash/sessions某项目下的会话列表
GET/projects/:hash/sessions/:id某会话的详情
DELETE/projects/:hash/sessions/:id删除某会话
PATCH/projects/:hash/sessions/:id/rename重命名某会话。请求体:{"name": "新名称"}

3.3 聊天与推理——核心接口

方法路径说明
GET/models列出可用 Provider 和模型
POST/chat发送提示词,返回 SSE 流(最核心的接口)
POST/chat/stop停止正在进行的 /chat 请求

3.4 配置管理

方法路径说明
GET/config读取当前 ~/.atomcode/config.toml
POST/config/reload重新加载配置文件
GET/providers列出所有已配置的 Provider
POST/providers添加一个新 Provider
PATCH/providers/:name更新指定 Provider 的配置
DELETE/providers/:name删除指定 Provider
POST/providers/:name/default将指定 Provider 设为默认
PATCH/providers/:name/thinking开关/调整指定 Provider 的思考模式

3.5 MCP 集成

方法路径说明
GET/mcp/status查看 MCP 服务器连接状态
POST/mcp/reload重新加载 .mcp.json 配置

3.6 认证与授权

方法路径说明
GET/auth/status当前登录状态
POST/auth/login/start启动 AtomGit OAuth 登录流程
POST/auth/login/:id/poll轮询登录结果
DELETE/auth/login/:id取消正在进行的登录
POST/auth/logout登出
POST/codingplan/setup认领 CodingPlan 并写入 Provider 配置

四、核心接口详解:POST /chat

这是 Daemon 的核心端点——它接收用户的任务描述,驱动 AtomCode 智能体执行,并通过 SSE 流实时返回结果。

4.1 请求格式

POST /chat HTTP/1.1
Host: 127.0.0.1:13456
Content-Type: application/json

{
  "message": "请帮我分析这个项目的目录结构,并给出优化建议"
}

4.2 响应格式——SSE 事件流

响应是一个 Server-Sent Events (SSE) 流,Content-Type 为 text/event-stream。每个事件的 data 字段包含一个 JSON 对象。

SSE 流的基本格式:

data: {"type":"text","content":"这个项目采用"}

data: {"type":"text","content":"MVC架构,"}

data: {"type":"tool_call_start","tool_name":"read_file","args":{"path":"src/main.rs"}}

data: {"type":"tool_call_end","tool_name":"read_file","result":"..."}

data: {"type":"text","content":"主要模块包括..."}

每个事件 JSON 的通用结构:

{
  "type": "<事件类型>",
  // ... 事件类型相关字段
}

4.3 请求流程示意

atomcode-core atomcode-daemon Python 客户端 atomcode-core atomcode-daemon Python 客户端 loop [每产生一段输出] loop [每次工具调用] POST /chat (用户提示词) 转发给智能体核心 调用 LLM 生成回复 text / reasoning SSE event (text: "分析结果...") tool_call_start SSE event (tool_call_start) 执行工具(读文件/执行命令等) tool_call_end SSE event (tool_call_end) tokens SSE event (tokens) POST /chat/stop (可选,手动停止)

五、SSE 事件类型全表

POST /chat 返回的 SSE 流中可能包含以下事件类型(来自 crates/atomcode-core/src/turn/event.rs):

5.1 文本输出

// AI 回复文本
{"type":"text","content":"这是一段生成的"}

// 完整文本块(有些场景直接发完整片段)
{"type":"text","content":"完整的回复内容"}

5.2 推理过程

// LLM 的内部推理过程(think tags 的内容)
{"type":"reasoning","content":"首先我需要理解这个函数的作用..."}

5.3 工具调用

// 工具调用开始
{
  "type": "tool_call_start",
  "tool_name": "read_file",
  "args": {"path": "src/main.rs"},
  "call_id": "call_xxx"
}

// 工具调用结束(含结果)
{
  "type": "tool_call_end",
  "tool_name": "read_file",
  "result": "文件内容...",
  "call_id": "call_xxx"
}

5.4 状态与统计

// Token 用量
{
  "type": "tokens",
  "prompt": 1523,
  "completion": 456,
  "total": 1979
}

// 状态更新
{"type":"status","status":"processing"}

// 错误事件
{"type":"error","error":"调用 API 超时,请检查网络连接"}

5.5 完整事件类型速查表

type含义关键字段
textAI 回复文本content: str
reasoning推理过程content: str
tool_call_start工具开始调用tool_name, args, call_id
tool_call_end工具调用完成tool_name, result, call_id
tokensToken 用量统计prompt: int, completion: int, total: int
status状态更新status: str
error错误通知error: str
done对话完成信号session_id: str

六、Python 客户端示例

6.1 基础版:流式打印

import json
import requests


def chat_with_atomcode(prompt: str, base_url="http://127.0.0.1:13456"):
    """最简实现:发送任务并流式打印到终端"""
    resp = requests.post(
        f"{base_url}/chat",
        json={"message": prompt},
        stream=True,
    )
    resp.raise_for_status()

    for line in resp.iter_lines():
        if not line:
            continue
        decoded = line.decode("utf-8")
        if decoded.startswith("data: "):
            event = json.loads(decoded[6:])
            t = event.get("type")

            if t == "text":
                print(event.get("content", ""), end="", flush=True)
            elif t == "reasoning":
                # 灰色显示推理过程,content 字段
                print(f"\033[90m{event.get('content', '')}\033[0m", end="", flush=True)
            elif t == "tool_call_start":
                print(f"\n\033[33m🔧 调用工具 [{event.get('tool_name', '?')}]\033[0m")
            elif t == "tool_call_end":
                print(f"\033[32m✔ 完成\033[0m")
            elif t == "error":
                print(f"\n\033[31m❌ 错误: {event.get('error', '未知')}\033[0m")
            elif t == "tokens":
                print(f"\n\033[90m[Token: 输入={event.get('prompt', '?')} 输出={event.get('completion', '?')}]\033[0m")


if __name__ == "__main__":
    chat_with_atomcode("分析当前项目的目录结构")

6.2 使用 SSE 客户端库

pip install sseclient-py
import json
import requests
import sseclient


def chat_sse(prompt: str, base_url="http://127.0.0.1:13456"):
    """使用 sseclient 库消费事件流"""
    resp = requests.post(
        f"{base_url}/chat",
        json={"message": prompt},
        stream=True,
    )
    client = sseclient.SSEClient(resp)

    for event in client.events():
        data = json.loads(event.data)

        match data.get("type"):
            case "text":
                print(data.get("content", ""), end="", flush=True)
            case "reasoning":
                print(f"\033[90m{data.get('content', '')}\033[0m", end="", flush=True)
            case "tool_call_start":
                print(f"\n🛠 [{data['tool_name']}] {data.get('args', {})}")
            case "tool_call_end":
                print(f"\n✅ 完成")
            case "error":
                print(f"\n❌ {data['error']}")

6.3 完整封装:面向对象的客户端

import json
import requests
from typing import Callable, Optional
from sseclient import SSEClient


class AtomCodeDaemonClient:
    """
    AtomCode Daemon 的 Python 客户端封装

    用法:
        client = AtomCodeDaemonClient()
        client.chat("帮我看下这个项目的结构",
            on_text=lambda d: print(d, end=""),
            on_tool_start=lambda n,a: print(f"\\n[调用 {n}]"),
        )
    """

    def __init__(self, base_url: str = "http://127.0.0.1:13456"):
        self.base_url = base_url.rstrip("/")

    # ── 工具方法 ──

    def health(self) -> bool:
        """检查 Daemon 是否在运行"""
        try:
            resp = requests.get(f"{self.base_url}/health", timeout=3)
            return resp.status_code == 200
        except requests.ConnectionError:
            return False

    def list_models(self) -> list:
        """列出可用 Provider 和 Model"""
        return requests.get(f"{self.base_url}/models").json()

    def list_sessions(self) -> list:
        """列出所有历史会话"""
        return requests.get(f"{self.base_url}/sessions").json()

    def new_session(self) -> dict:
        """创建一个新会话,返回会话信息"""
        return requests.post(f"{self.base_url}/sessions").json()

    def delete_session(self, session_id: str):
        """删除指定会话"""
        # 先查 session 所属项目
        sessions = self.list_sessions()
        for s in sessions:
            if s.get("id") == session_id:
                project_hash = s.get("project_hash")
                if project_hash:
                    requests.delete(
                        f"{self.base_url}/projects/{project_hash}/sessions/{session_id}"
                    )
                return

    # ── 核心:流式聊天 ──

    def chat(
        self,
        prompt: str,
        on_text: Optional[Callable[[str], None]] = None,
        on_reasoning: Optional[Callable[[str], None]] = None,
        on_tool_start: Optional[Callable[[str, dict], None]] = None,
        on_tool_end: Optional[Callable[[str, str], None]] = None,
        on_token_usage: Optional[Callable[[dict], None]] = None,
        on_error: Optional[Callable[[str], None]] = None,
        on_status: Optional[Callable[[str], None]] = None,
    ) -> str:
        """
        发送提示词并消费 SSE 事件流

        参数:
            prompt: 用户提示词
            各种 on_xx 回调,收到对应事件时被调用

        返回:
            拼接后的完整回复文本
        """
        full_text: list[str] = []

        resp = requests.post(
            f"{self.base_url}/chat",
            json={"message": prompt},
            stream=True,
        )
        resp.raise_for_status()

        client = SSEClient(resp)
        for event in client.events():
            data = json.loads(event.data)
            t = data.get("type")

            if t == "text":
                content = data.get("content", "")
                full_text.append(content)
                if on_text:
                    on_text(content)

            elif t == "reasoning":
                if on_reasoning:
                    on_reasoning(data.get("content", ""))

            elif t == "tool_call_start":
                if on_tool_start:
                    on_tool_start(data.get("tool_name"), data.get("args", {}))

            elif t == "tool_call_end":
                if on_tool_end:
                    on_tool_end(data.get("tool_name"), data.get("result", ""))

            elif t == "tokens":
                if on_token_usage:
                    on_token_usage(data)

            elif t == "error":
                if on_error:
                    on_error(data.get("error", "未知错误"))

            elif t == "status":
                if on_status:
                    on_status(data.get("status", ""))

        return "".join(full_text)

    def chat_sync(self, prompt: str) -> str:
        """同步模式:等待完整回复后返回纯文本"""
        return self.chat(prompt)

    # ── 控制 ──

    def stop(self):
        """停止正在进行的 chat 请求"""
        try:
            requests.post(f"{self.base_url}/chat/stop", timeout=3)
        except requests.RequestException:
            pass

    def shutdown(self):
        """关闭 Daemon"""
        try:
            requests.post(f"{self.base_url}/shutdown", timeout=3)
        except requests.RequestException:
            pass


# ── 使用示例 ──

if __name__ == "__main__":
    import sys

    client = AtomCodeDaemonClient()

    # 先检查 Daemon 是否运行
    if not client.health():
        print("❌ AtomCode Daemon 未运行!请先执行: atomcode daemon")
        sys.exit(1)

    print("✅ 已连接 AtomCode Daemon")
    print(f"📋 可用模型: {client.list_models()}\n")

    # 流式对话
    final = client.chat(
        prompt="分析当前项目的目录结构和代码组成",
        on_text=lambda d: print(d, end="", flush=True),
        on_reasoning=lambda d: print(f"\033[90m{d}\033[0m", end="", flush=True),
        on_tool_start=lambda n, a: print(f"\n\033[33m🔧 [{n}]\033[0m"),
        on_tool_end=lambda n, r: print(f"\033[32m✔ [{n}]\033[0m"),
        on_token_usage=lambda u: print(f"\n\033[90m[Token: {u}]\033[0m"),
        on_error=lambda e: print(f"\n\033[31m❌ {e}\033[0m"),
    )

    print(f"\n\n📝 最终回复长度: {len(final)} 字符")

6.4 完整测试脚本:一站式验证所有 API

项目附带的 test_atomcode_daemon.py 是一个可独立运行的测试脚本,覆盖了 Daemon API 的全部核心功能:

测试项函数作用
健康检查test_health()确认 Daemon 是否在运行
模型列表test_models()列出可用 Provider 和模型
会话列表test_sessions()查看历史会话
流式对话test_chat_stream()核心测试:发送提示词,实时打印 reasoning/text/tool 事件
同步模式test_chat_sync()等待完整回复后纯文本输出
#!/usr/bin/env python3
"""
AtomCode Daemon 交互测试脚本

测试流程:
  1. 检查 Daemon 是否运行
  2. 获取可用模型列表
  3. 发送测试提示词,流式接收回复
  4. 显示工具调用和 token 用量
  5. 测试同步模式(非流式)

用法:
  python3 test_atomcode_daemon.py                          # 默认地址
  python3 test_atomcode_daemon.py --url http://localhost:13456
  python3 test_atomcode_daemon.py --prompt "帮我写个快排" --sync
"""
import argparse
import json
import sys
import time
from typing import Optional

try:
    import requests
except ImportError:
    print("❌ 需要安装 requests 库: pip install requests")
    sys.exit(1)

# ──────────────────────────────────────────────
#  客户端
# ──────────────────────────────────────────────


class AtomCodeDaemonClient:
    """AtomCode Daemon 的 HTTP 客户端"""

    def __init__(self, base_url: str = "http://127.0.0.1:13456"):
        self.base_url = base_url.rstrip("/")
        self._session = requests.Session()

    # ── 健康检查 ──

    def health(self) -> bool:
        try:
            r = self._session.get(f"{self.base_url}/health", timeout=5)
            return r.status_code == 200
        except requests.ConnectionError:
            return False

    # ── 模型列表 ──

    def list_models(self) -> Optional[list]:
        try:
            r = self._session.get(f"{self.base_url}/models", timeout=5)
            r.raise_for_status()
            return r.json()
        except Exception as e:
            print(f"  ⚠ 获取模型列表失败: {e}")
            return None

    # ── 会话列表 ──

    def list_sessions(self) -> Optional[list]:
        try:
            r = self._session.get(f"{self.base_url}/sessions", timeout=5)
            r.raise_for_status()
            return r.json()
        except Exception as e:
            print(f"  ⚠ 获取会话列表失败: {e}")
            return None

    # ── 流式聊天 ──

    def chat_stream(self, prompt: str):
        """
        流式聊天,逐个事件 yield 返回。

        Yields:
            dict: SSE 事件 JSON
        """
        resp = self._session.post(
            f"{self.base_url}/chat",
            json={"message": prompt},
            stream=True,
        )
        resp.raise_for_status()

        for line in resp.iter_lines():
            if not line:
                continue
            decoded = line.decode("utf-8")
            if decoded.startswith("data: "):
                yield json.loads(decoded[6:])

    # ── 同步聊天(等待完整回复)──

    def chat_sync(self, prompt: str) -> str:
        full_text: list[str] = []
        for event in self.chat_stream(prompt):
            t = event.get("type")
            if t in ("text", "text_delta"):
                full_text.append(event.get("content") or event.get("delta", ""))
        return "".join(full_text)

    # ── 停止 ──

    def stop(self):
        try:
            self._session.post(f"{self.base_url}/chat/stop", timeout=3)
        except Exception:
            pass

    # ── 关闭 daemon ──

    def shutdown(self):
        try:
            self._session.post(f"{self.base_url}/shutdown", timeout=3)
        except Exception:
            pass


# ──────────────────────────────────────────────
#  测试用例
# ──────────────────────────────────────────────


def test_health(client: AtomCodeDaemonClient) -> bool:
    """测试 1: 健康检查"""
    print("\n" + "=" * 60)
    print("📡 测试 1: 健康检查")
    print("=" * 60)
    ok = client.health()
    if ok:
        print("  ✅ Daemon 正在运行")
    else:
        print("  ❌ Daemon 未响应!请先启动: atomcode daemon")
    return ok


def test_models(client: AtomCodeDaemonClient):
    """测试 2: 获取模型列表"""
    print("\n" + "=" * 60)
    print("📋 测试 2: 获取可用模型")
    print("=" * 60)
    models = client.list_models()
    if models:
        if isinstance(models, list):
            for m in models:
                name = m.get("name", m.get("model", str(m)))
                provider = m.get("provider", "")
                print(f"  ✅ {name}" + (f" ({provider})" if provider else ""))
        else:
            print(f"  ✅ {models}")
    else:
        print("  ⚠ 无法获取模型列表(非致命)")


def test_sessions(client: AtomCodeDaemonClient):
    """测试 3: 会话列表"""
    print("\n" + "=" * 60)
    print("💬 测试 3: 查看历史会话")
    print("=" * 60)
    sessions = client.list_sessions()
    if sessions:
        if isinstance(sessions, list):
            if sessions:
                print(f"  ✅ 共有 {len(sessions)} 个历史会话")
                for i, s in enumerate(sessions[:3]):  # 最多显示 3 个
                    sid = s.get("id", s.get("session_id", "?"))
                    print(f"     #{i+1}  session: {sid}")
                if len(sessions) > 3:
                    print(f"     ... 还有 {len(sessions)-3} 个")
            else:
                print("  ℹ  暂无历史会话")
    else:
        print("  ⚠ 无法获取会话列表(非致命)")


def test_chat_stream(client: AtomCodeDaemonClient, prompt: str):
    """测试 4: 流式对话"""
    print("\n" + "=" * 60)
    print("🗣  测试 4: 流式对话")
    print(f'    提示词: "{prompt}"')
    print("=" * 60)
    print()

    full_text: list[str] = []
    tool_count = 0
    token_info = {}

    start_time = time.time()
    try:
        for event in client.chat_stream(prompt):
            t = event.get("type")

            if t == "reasoning":
                # 推理过程用灰色 + 斜体显示
                print(f"\033[90m\033[3m{event.get('content', '')}\033[0m", end="", flush=True)

            elif t == "text":
                # 完整文本块,直接打印
                content = event.get("content", "")
                full_text.append(content)
                print(content, end="", flush=True)

            elif t == "tool_call_start":
                tool_count += 1
                name = event.get("tool_name", "?")
                args = event.get("args", {})
                args_str = json.dumps(args, ensure_ascii=False)
                print(f"\n  ── \033[33m🔧 工具调用 #{tool_count}: {name}\033[0m")
                # 只显示参数前 200 字符
                if len(args_str) > 200:
                    args_str = args_str[:200] + "..."
                print(f"     参数: {args_str}")

            elif t == "tool_call_end":
                name = event.get("tool_name", "?")
                result = event.get("result", "")
                result_str = str(result)
                # 只显示结果摘要
                summary = result_str[:150].replace("\n", " ")
                if len(result_str) > 150:
                    summary += "..."
                print(f"  ── \033[32m✔ {name} 完成\033[0m")
                print(f"     结果: {summary}")

            elif t == "tokens":
                token_info = event
                total = event.get("total", "?")
                inp = event.get("prompt", "?")
                out = event.get("completion", "?")
                print(f"\n  ── \033[90m📊 Token: 输入={inp}  输出={out}  总计={total}\033[0m")

            elif t == "status":
                status = event.get("status", "")
                if status:
                    print(f"\n  ── \033[90m[状态: {status}]\033[0m")

            elif t == "error":
                print(f"\n  ── \033[31m❌ 错误: {event.get('error', '未知')}\033[0m")

    except KeyboardInterrupt:
        print("\n  ⏹  用户中断,正在停止...")
        client.stop()
    except requests.RequestException as e:
        print(f"\n  ❌ 网络错误: {e}")

    elapsed = time.time() - start_time
    print()
    print("-" * 60)
    print(f"  ✅ 回复完成 | 耗时: {elapsed:.1f}s | 字符数: {sum(len(t) for t in full_text)}")
    if tool_count:
        print(f"  🔧 工具调用次数: {tool_count}")
    if token_info:
        print(f"  📊 Token: {token_info.get('total_tokens', '?')}")


def test_chat_sync(client: AtomCodeDaemonClient, prompt: str):
    """测试 5: 同步模式"""
    print("\n" + "=" * 60)
    print("📝 测试 5: 同步模式(等待完整回复)")
    print(f'    提示词: "{prompt}"')
    print("=" * 60)
    print()

    start = time.time()
    try:
        result = client.chat_sync(prompt)
        elapsed = time.time() - start
        print(result)
        print()
        print("-" * 60)
        print(f"  ✅ 完成 | 耗时: {elapsed:.1f}s | 字符数: {len(result)}")
    except Exception as e:
        print(f"  ❌ 出错: {e}")


# ──────────────────────────────────────────────
#  主入口
# ──────────────────────────────────────────────


def main():
    parser = argparse.ArgumentParser(
        description="AtomCode Daemon 交互测试脚本",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=(
            "示例:\n"
            "  %(prog)s                         # 运行全部测试\n"
            "  %(prog)s --url http://localhost:13456\n"
            "  %(prog)s --prompt \"写个二分查找\"  # 自定义提示词\n"
            "  %(prog)s --sync                   # 仅测试同步模式\n"
            "  %(prog)s --stream                 # 仅测试流式模式\n"
            "  %(prog)s --quick                  # 快速模式(跳过部分测试)"
        ),
    )
    parser.add_argument("--url", default="http://127.0.0.1:13456",
                        help="Daemon 地址 (默认: http://127.0.0.1:13456)")
    parser.add_argument("--prompt", default="请简要介绍一下这个项目的目录结构",
                        help="测试用的提示词")
    parser.add_argument("--stream", action="store_true",
                        help="仅测试流式模式")
    parser.add_argument("--sync", action="store_true",
                        help="仅测试同步模式")
    parser.add_argument("--quick", action="store_true",
                        help="快速模式:跳过模型列表和会话列表")

    args = parser.parse_args()
    client = AtomCodeDaemonClient(args.url)

    print()
    print("╔" + "═" * 58 + "╗")
    print("║        AtomCode Daemon 交互测试脚本               ║")
    print("║" + " " * 58 + "║")
    print(f"║  目标: {args.url:<46}║")
    print("╚" + "═" * 58 + "╝")

    # 步骤 1:健康检查(必须通过)
    if not test_health(client):
        print()
        print("💡 提示:请先启动 Daemon:")
        print("   atomcode daemon")
        print("或者指定正确的地址:")
        print(f"   {sys.argv[0]} --url http://your-host:port")
        sys.exit(1)

    # 根据参数决定运行哪些测试
    run_all = not (args.stream or args.sync)

    if run_all or not args.quick:
        # 正常模式:运行多数测试
        if run_all or args.stream:
            if not args.quick:
                test_models(client)
                test_sessions(client)
            test_chat_stream(client, args.prompt)

        if run_all or args.sync:
            test_chat_sync(client, args.prompt)

    elif args.quick:
        # 快速模式:只跑核心流式测试
        print("\n  ⚡ 快速模式")
        test_chat_stream(client, args.prompt)

    print()
    print("=" * 60)
    print("✅ 全部测试完成")
    print("=" * 60)
    print()


if __name__ == "__main__":
    main()

用法
# 运行全部测试(健康检查 → 模型列表 → 会话列表 → 流式对话 → 同步模式)
python3 test_atomcode_daemon.py

# 仅测试流式模式
python3 test_atomcode_daemon.py --stream

# 仅测试同步模式
python3 test_atomcode_daemon.py --sync

# 快速模式:跳过模型/会话列表,只跑核心对话
python3 test_atomcode_daemon.py --quick

# 自定义提示词和 Daemon 地址
python3 test_atomcode_daemon.py \
  --url http://localhost:13456 \
  --prompt "帮我把这个 Rust 项目结构画成 ASCII 目录树"
脚本核心架构

脚本包含两个核心组件:

AtomCodeDaemonClient — 轻量级 HTTP 客户端封装:

class AtomCodeDaemonClient:
    def __init__(self, base_url="http://127.0.0.1:13456"):
        self.base_url = base_url.rstrip("/")
        self._session = requests.Session()

    def health(self) -> bool           # GET /health
    def list_models(self) -> list      # GET /models
    def list_sessions(self) -> list    # GET /sessions
    def chat_stream(self, prompt)      # POST /chat (SSE)
    def chat_sync(self, prompt) -> str # POST /chat (等待完整回复)
    def stop(self)                     # POST /chat/stop
    def shutdown(self)                 # POST /shutdown

注意 chat_stream()生成器(yield 每个 SSE 事件),chat_sync() 在其基础上聚合文本。调用方可以灵活选用。

② 测试函数 — 五项独立测试用例,每项有清晰的输出格式:

# 流式对话测试(核心)
def test_chat_stream(client, prompt):
    for event in client.chat_stream(prompt):
        t = event.get("type")
        if t == "reasoning":        # 推理过程(灰色斜体)
            print(f"\033[90m\033[3m{event['content']}\033[0m")
        elif t == "text":           # 文本输出
            print(event["content"], end="", flush=True)
        elif t == "tool_call_start":# 工具开始调用
            print(f"\n  🔧 {event['tool_name']}: {event['args']}")
        elif t == "tool_call_end":  # 工具完成
            print(f"  ✔ {event['tool_name']} 完成")
        elif t == "tokens":         # Token 统计
            print(f"📊 Token: {event}")
运行效果示例
╔══════════════════════════════════════════════════════════╗
║        AtomCode Daemon 交互测试脚本               ║
║  目标: http://127.0.0.1:13456                        ║
╚══════════════════════════════════════════════════════════╝

============================================================
📡 测试 1: 健康检查
============================================================
  ✅ Daemon 正在运行

============================================================
📋 测试 2: 获取可用模型
============================================================
  ✅ deepseek-v3 (deepseek)
  ✅ deepseek-v4-flash (deepseek)

============================================================
💬 测试 3: 查看历史会话
============================================================
  ✅ 共有 5 个历史会话

============================================================
🗣  测试 4: 流式对话
    提示词: "请简要介绍一下这个项目的目录结构"
============================================================

## 项目目录结构简介

这是一个 **OpenHarmony** 项目...

  ── 📊 Token: 输入=7106  输出=47  总计=7153
------------------------------------------------------------
  ✅ 回复完成 | 耗时: 14.9s | 字符数: 1432

============================================================
📝 测试 5: 同步模式(等待完整回复)
    提示词: "请简要介绍一下这个项目的目录结构"
============================================================
...(输出同上)...
------------------------------------------------------------
  ✅ 完成 | 耗时: 14.9s | 字符数: 1432

============================================================
✅ 全部测试完成
============================================================

test_atomcode_daemon.py 脚本相当于一个可运行的 API 参考实现——你可以把它作为起点,裁剪出适合自己场景的客户端代码。具体文件见项目目录下的 test_atomcode_daemon.py


七、其他语言客户端

7.1 Node.js / TypeScript

// 最简单的 HTTP + SSE 客户端
const response = await fetch("http://127.0.0.1:13456/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    message: "分析这个项目",
  }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const text = decoder.decode(value);
  for (const line of text.split("\n")) {
    if (line.startsWith("data: ")) {
      const event = JSON.parse(line.slice(6));
      if (event.type === "text") {
        process.stdout.write(event.content);
      }
    }
  }
}

7.2 Go

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
)

func main() {
	// 发起 POST /chat 请求
	body := `{"message":"分析这个项目"}`
	resp, _ := http.Post("http://127.0.0.1:13456/chat",
		"application/json",
		strings.NewReader(body))
	defer resp.Body.Close()

	// 逐行读取 SSE 流
	scanner := bufio.NewScanner(resp.Body)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "data: ") {
			var event map[string]interface{}
			json.Unmarshal([]byte(line[6:]), &event)
			if event["type"] == "text" {
			 fmt.Print(event["content"])
			}
		}
	}
}

7.3 Rust

use reqwest::Client;
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    let body = serde_json::json!({
        "message": "分析这个项目"
    });

    let mut stream = client
        .post("http://127.0.0.1:13456/chat")
        .json(&body)
        .send()
        .await?
        .bytes_stream();

    use futures_util::StreamExt;
    let mut buf = String::new();
    while let Some(chunk) = stream.next().await {
        let chunk = chunk?;
        buf.push_str(&String::from_utf8_lossy(&chunk));

        while let Some(newline) = buf.find('\n') {
            let line = buf[..newline].trim().to_string();
            buf = buf[newline + 1..].to_string();

            if let Some(data) = line.strip_prefix("data: ") {
                if let Ok(event) = serde_json::from_str::<Value>(data) {
                    if event["type"] == "text" {
                        print!("{}", event["content"].as_str().unwrap_or(""));
                        use std::io::Write;
                        std::io::stdout().flush()?;
                    }
                }
            }
        }
    }
    Ok(())
}

7.4 cURL(测试用)

# 基本用法:流式输出到终端
curl -N -X POST http://127.0.0.1:13456/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"分析项目结构"}'

# 用 jq 格式化事件
curl -N -s -X POST http://127.0.0.1:13456/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"Hello"}' | \
  while IFS= read -r line; do
    [[ "$line" = data:* ]] && echo "$line" | sed 's/^data: //' | jq .
  done

八、安全注意事项

⚠️ 无内置认证

Daemon 没有任何内置的身份认证。默认绑定 127.0.0.1 就是唯一的安全措施——只有本机能访问。

# ✅ 安全:只监听本机
atomcode daemon --host 127.0.0.1

# ❌ 危险!暴露到局域网或公网
atomcode daemon --host 0.0.0.0

🔒 为什么不能暴露

成功调用 /chat 的客户端可以获得完整的工具执行权限,包括:

  • bash — 执行任意shell命令
  • write — 写入任意文件
  • edit — 修改任意文件
  • web_fetch — 发起网络请求
  • web_search — 搜索互联网

这相当于完全控制你的开发机器

🔐 远程访问方案

如果确实需要远程访问,建议使用反向代理:

客户端 ──HTTPS/TLS──► 反向代理 ──http──► atomcode-daemon
                        (nginx/caddy)
                        ├── 认证 (OAuth/Basic Auth)
                        ├── TLS 加密
                        └── 来源限制

🛡️ CORS 策略

Daemon 配置了 CORS,只允许 loopback 来源localhost127.0.0.1::1,任意端口和协议)。即使通过 DNS 重绑定攻击,CORS 层也会拦截。


九、与主流协议的对比

AtomCode Daemon 的协议是自定义 HTTP + SSE,可以与当前主流的两大 AI Agent 协议进行对比:

9.1 与 IBM ACP (Agent Communication Protocol) 对比

IBM 的 ACP 是智能体间通信协议(REST,智能体 ↔ 智能体):

文档地址:https://www.ibm.com/cn-zh/think/topics/agent-communication-protocol

维度AtomCode DaemonIBM ACP
设计目的客户端 ↔ 智能体智能体 ↔ 智能体
传输层HTTP + SSEHTTP REST
Agent 发现GET /modelsGET /agents
执行接口POST /chat (SSE)POST /runs (JSON/SSE)
消息格式自定义 TurnEvent (JSON)标准 Message/Part
会话管理REST CRUD 端点runs / threads
认证无(仅 loopback)可插拔 SecurityScheme
SDK无需,纯 HTTPpip install acp-sdk

9.2 与 JetBrains ACP (Agent Client Protocol) 对比

JetBrains/Zed 的 ACP 是编辑器与编码智能体间的协议(stdio JSON-RPC):

维度AtomCode DaemonJetBrains ACP
设计目的通用客户端驱动编辑器 ↔ 编码智能体
传输层HTTP + SSEstdio JSON-RPC 2.0
启动方式独立 HTTP 服务编辑器 spawn 子进程
核心方法POST /chatsession/prompt
流式方式SSE (text/event-stream)session/update 通知
工具回调SSE 事件session/update + permission_request
适用客户端任何 HTTP 客户端编辑器(Zed, JetBrains, VS Code)

9.3 总体来说

AtomCode Daemon = 自定协议 (HTTP + SSE)
     ─ 最简单直接,任何语言都能对接
     ─ 专为 AtomCode 自身设计

IBM ACP        = 智能体间通信标准 (REST)
     ─ 跨框架/跨组织智能体协作
     ─ 需要双方都实现 ACP

JetBrains ACP  = 编辑器侧编码智能体标准 (stdio JSON-RPC)
     ─ 编辑器与编码智能体连线
     ─ 需要实现子进程通信

十、典型应用场景

10.1 IDE 插件集成

VS Code 扩展、JetBrains 插件通过 Daemon API 连接到 AtomCode,实现编辑器内 AI 编码辅助。

编辑器中点"AI 助手" → 发 POST /chat → SSE 流式展示代码生成

10.2 自动化脚本与 CI/CD

# 在 CI 中启动 daemon
atomcode daemon &
sleep 3  # 等启动

# 批量执行任务
python client.py "修复所有 lint 错误"
python client.py "生成单元测试"
python client.py "更新 README"

# 关闭 daemon
curl -X POST http://127.0.0.1:13456/shutdown

10.3 自定义 Web UI

# AtomCode 自带 WebUI
atomcode webui
# 或在浏览器中访问自建前端 → 发 POST /chat

10.4 多 Agent 编排

# 一个脚本来编排多个任务
client = AtomCodeDaemonClient()

tasks = [
    "分析 src/ 目录中的代码质量",
    "为 database.rs 生成单元测试",
    "重构 utils.rs 中的重复代码",
]

for task in tasks:
    print(f"\n{'='*60}")
    print(f"任务: {task}")
    print(f"{'='*60}")
    result = client.chat_sync(task)
    print(f"\n完成,结果长度: {len(result)} 字符")

10.5 长期会话保持

Daemon 的会话与 CLI 共享,跨启动持久化:

# 第一次对话
client.chat("帮我理解这个项目", on_text=print)

# 第二天继续——会话还在
client = AtomCodeDaemonClient()
sessions = client.list_sessions()
print(f"历史会话数: {len(sessions)}")

附:快速参考卡片

启动

atomcode daemon                    # 默认端口 13456
atomcode daemon --port 12345       # 自定义端口

检查

curl http://127.0.0.1:13456/health
curl http://127.0.0.1:13456/models

调用

curl -N -X POST http://127.0.0.1:13456/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"你好"}'

停止

curl -X POST http://127.0.0.1:13456/chat/stop   # 停止当前对话
curl -X POST http://127.0.0.1:13456/shutdown     # 关闭 daemon

文档版本: v1.1 | 适用 AtomCode 版本: v4.25.9+
参考: AtomCode 官方文档 - Headless & Daemon
仓库: https://atomgit.com/atomgit_atomcode/atomcode

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

特立独行的猫a

您的鼓励是我的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值