Back to OSS
Swift Package エージェント / プロトコル

swift-a2a

Google A2A(Agent-to-Agent)プロトコルの Swift クライアント実装。エージェント同士を相互運用させる

Swift
agenta2aprotocol

English | 日本語

swift-a2a

A2A (Agent2Agent) プロトコル v1.0 の Swift 実装(クライアント + サーバー + in-process)。

Swift A2A Platforms License

特徴

  • A2A v1.0 完全準拠 — 正規 Protocol Buffer 定義に基づく ProtoJSON シリアライズ(ROLE_USER 形式の enum、camelCase、判別子レス oneof)
  • 2 バインディングREST(HTTP+JSON)JSON-RPC 2.0 を提供。両者は機能的に等価(仕様 §5.1)で、エージェントが対応する方を選べる
  • 層をターゲットで分離 — 規定プロトコルの型だけ使う A2ACore、実装を使う A2AClientREST / A2AClientJSONRPC。アンブレラなし、依存は使う層だけが背負う
  • SSE ストリーミングmessage:stream / tasks:subscribe をリアルタイム受信
  • 型安全 & Swift らしい設計 — oneof を enum、型付き ID、@resultBuilder でメッセージ構築
  • 依存最小 — Foundation + swift-structured-data のみ。gRPC・swift-syntax・マクロ非依存

アーキテクチャ

Product 役割 依存
A2ACore 規定プロトコル層: 全ワイヤ型 + ProtoJSON Codable + 構築ビルダー StructuredDataCore
A2AClientREST REST(HTTP+JSON)バインディングのクライアント A2ACore
A2AClientJSONRPC JSON-RPC 2.0 バインディングのクライアント A2ACore
A2AServer サーバ実装フレームワーク: AgentExecutor / RequestHandler / TaskStore / TaskUpdater / EventQueue など(トランスポート非依存) A2ACore
A2AServerJSONRPC JSON-RPC バインディングのサーバ側ディスパッチャ(HTTP 非依存) A2AServer
A2AServerREST REST バインディングのサーバ側ディスパッチャ(HTTP 非依存) A2AServer
A2AInProcess 同一プロセス内で client(A2ATransport) ↔ server(RequestHandler) を型直結(HTTP/シリアライズ無し) A2AClientCore + A2AServer

クライアントだけ欲しいときは使うバインディングの product を import します(両方入れて Agent Card から選択することも可能)。サーバを実装するときは A2AServerAgentExecutor を実装し、DefaultRequestHandler に渡します。HTTP を介さず同一プロセスで動かすなら A2AInProcessA2AClient.inProcess(handler:) を使います(リモートに切り替えたくなったら transport を差し替えるだけ)。

インストール

// Package.swift
dependencies: [
    .package(url: "https://github.com/no-problem-dev/swift-a2a.git", from: "0.3.0")
]
.target(name: "YourTarget", dependencies: [
    .product(name: "A2AClientREST", package: "swift-a2a"),      // REST を使う場合
    // .product(name: "A2AClientJSONRPC", package: "swift-a2a"), // JSON-RPC を使う場合
])

クイックスタート

クライアント生成

import A2ACore
import A2AClientREST   // または A2AClientJSONRPC

// REST バインディング
let client = A2AClient.rest(
    baseURL: URL(string: "https://agent.example.com/a2a/v1")!,
    authentication: .bearer("your-token")
)

// JSON-RPC バインディング
// let client = A2AClient.jsonRPC(
//     endpoint: URL(string: "https://agent.example.com/rpc")!,
//     authentication: .bearer("your-token")
// )

Agent Card の取得

let card = try await client.fetchAgentCard()   // /.well-known/agent-card.json
print(card.name)
print(card.capabilities.streaming ?? false)
print(card.supportedInterfaces.map(\.protocolBinding))   // ["JSONRPC", "GRPC", "HTTP+JSON"]

メッセージ送信

let response = try await client.sendMessage(.user("売上レポートを作成して"))

switch response {
case .task(let task):
    print(task.status.state)        // .completed など
    print(task.artifacts.first?.parts.first?.text ?? "")
case .message(let message):
    print(message.text)
}

ビルダーで複数パートのメッセージも組み立てられます(文字列リテラルは自動でテキストパート):

let message = Message(role: .user) {
    "この画像を解析して"
    Part.file(uri: "https://example.com/photo.png", mediaType: "image/png")
    Part.data(["threshold": 0.8])
}
let response = try await client.sendMessage(message)

ストリーミング

for try await event in try await client.streamMessage(.user("詳細なレポートを書いて")) {
    switch event {
    case .task(let task): print("task: \(task.status.state)")
    case .statusUpdate(let update): print("status: \(update.status.state)")
    case .artifactUpdate(let update): print("artifact chunk: \(update.artifact.parts.first?.text ?? "")")
    case .message(let message): print("message: \(message.text)")
    }
}

タスク操作

let task = try await client.getTask("task-id", historyLength: 10)
let canceled = try await client.cancelTask("task-id")
let list = try await client.listTasks(ListTasksRequest(status: .working))
for try await event in try await client.subscribeToTask("task-id") { /* ... */ }

プッシュ通知設定

let config = try await client.createPushNotificationConfig(
    TaskPushNotificationConfig(url: "https://my-webhook.example.com/a2a", taskId: "task-id")
)
let configs = try await client.listPushNotificationConfigs(taskId: "task-id")
try await client.deletePushNotificationConfig(taskId: "task-id", id: config.id ?? "")

エラーハンドリング

do {
    _ = try await client.getTask("missing")
} catch let A2AError.rpc(error) {
    print(error.code)     // -32001
    print(error.reason)   // "TASK_NOT_FOUND"(google.rpc.ErrorInfo より)
} catch let A2AError.http(status, body) {
    print(status, body ?? "")
}

対応操作

操作 メソッド JSON-RPC REST
メッセージ送信 sendMessage SendMessage POST /message:send
ストリーミング送信 streamMessage SendStreamingMessage POST /message:stream
タスク取得 getTask GetTask GET /tasks/{id}
タスク一覧 listTasks ListTasks GET /tasks
タスクキャンセル cancelTask CancelTask POST /tasks/{id}:cancel
タスク購読 subscribeToTask SubscribeToTask POST /tasks/{id}:subscribe
プッシュ設定 作成/取得/一覧/削除 *PushNotificationConfig *TaskPushNotificationConfig /tasks/{id}/pushNotificationConfigs
拡張 Agent Card fetchExtendedAgentCard GetExtendedAgentCard GET /extendedAgentCard

ライセンス

MIT

© 2026 Kyoichi Taniguchi. All rights reserved.