Skip to content

Cocoanetics/SwiftACP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SwiftACP

A Swift implementation of the Agent Client Protocol (ACP) in a single module — import SwiftACP — covering all three roles:

  • the client — the protocol types + a JSON-RPC client (ACPAgent / ACPAgentConnection) for driving an ACP agent (the editor/host side; spawning is desktop-only).
  • the server — the agent/server harness for exposing an app or CLI as an ACP agent (ACPAgentHandler, ACPServerSession, ACPAgentServer).
  • the daemon client — the generated ACPXDaemon.Client for driving a remote acpxd session daemon over MCP, on every platform including iOS and Android.

The library builds on JSONFoundation (zero-dependency: JSON value/schema types and the JSON-RPC runtime) and SwiftMCP's swift-nio-free MCP client, so it embeds anywhere — a Mac app, an iOS app, an agent CLI, a test. The same package also ships the acpx CLI and acpxd daemon (macOS-only — see below), a headless toolkit for driving ACP agents modelled after the original openclaw/acpx.

Adding the dependency

.package(url: "https://github.com/Cocoanetics/SwiftACP.git", from: "0.1.0"),

with "SwiftACP" in your target's dependencies. The default-on Server package trait pulls SwiftMCP's swift-nio server transports (what acpxd serves over). A client-only consumer — an iOS or Android app driving a remote acpxd — should disable it for a swift-nio-free graph:

.package(url: "https://github.com/Cocoanetics/SwiftACP.git", from: "0.1.0", traits: []),

Expose an agent (server)

import SwiftACP

struct MyHandler: ACPAgentHandler {
    func initialize(_ request: InitializeRequest) async -> InitializeResponse {
        InitializeResponse(agentInfo: Implementation(name: "my-agent", version: "1.0"))
    }
    func newSession(_ request: NewSessionRequest) async throws -> NewSessionResponse {
        NewSessionResponse(sessionId: UUID().uuidString)
    }
    func prompt(_ request: PromptRequest, session: ACPServerSession) async throws -> PromptResponse {
        await session.sendText("Hello!")
        return PromptResponse(stopReason: .endTurn,
                              usage: PromptUsage(inputTokens: 10, outputTokens: 2, totalTokens: 12))
    }
}

@main enum Main {
    static func main() async throws {
        try await ACPAgentServer.serveStdio(handler: MyHandler())   // speaks ACP over stdin/stdout
    }
}

Only initialize, newSession, and prompt are required; authenticate, loadSession, cancel, setMode, setConfigOption, setModel, and availableCommands have defaults. ACPServerSession streams session/updates (text, reasoning, tool calls, plans), calls back to the client — permission prompts (requestPermission) and file I/O (readTextFile / writeTextFile) — and exposes cooperative cancellation. LoopbackTransport.pair() (JSONFoundation's in-memory transport, re-exported by SwiftACP) runs a client and server in the same process for embedding an agent inside an app or for hermetic tests.

Drive an agent (client)

import SwiftACP

let agent = try await ACPAgent.launch(agent: "claude", cwd: repoPath, permission: .approveReads)
let session = try await agent.newSession()
let outcome = try await session.run("Explain this project") { update in render(update) }
print(outcome.text, outcome.stopReason)
await agent.close()

The acpx CLI and acpxd daemon (macOS)

The same package ships a headless CLI and a session daemon built on the library — a byte-faithful Swift clone of openclaw/acpx 0.11.0. They're macOS-only (Bonjour service advertisement, POSIX signals) and are gated behind #if os(macOS) in Package.swift, so the SwiftACP library itself stays nio-free and keeps building on Linux and Windows.

swift run acpx claude "explain what this project does"
swift run acpx codex --approve-reads "find and fix the flaky test"
git diff | swift run acpx claude -q "review this diff"
swift run acpx chat codex          # interactive multi-turn session
swift run acpx agents              # list known agents + resolved launch commands

acpxd is the session daemon: an MCP server (Bonjour + local TCP) holding live ACP sessions, with an optional outward HTTP+SSE transport.

swift run acpxd                       # Bonjour + local TCP (how the acpx CLI discovers it)
swift run acpxd --http-port 9090 -v   # also expose MCP over HTTP+SSE (unauthenticated — keep on loopback)

The CLI and daemon are built on the library plus SwiftMCP's server side: acpxd is an @MCPServer whose tools the CLI calls over MCP — the same generated ACPXDaemon.Client an iOS app uses to drive a remote daemon. The extra executable-only dependencies (service-lifecycle, argument-parser) never reach consumers of the SwiftACP library product.

Status

A byte-faithful Swift clone of openclaw/acpx, validated by loopback + a mock agent driven by the real ACP client (Tests/ACPTests/Fixtures/mock-agent.py). SwiftAgents' Coder example exposes itself over ACP via the server half.

License

BSD 2-Clause — see LICENSE.

About

A Swift implementation of the Agent Client Protocol (ACP) — drive ACP agents, or expose your app/CLI as one.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors