Description
Create the AgentChat SPM package — pure domain + TCA layer, no SwiftUI, no gRPC, no SwiftTerm. Defines the abstract model for an agent conversation (messages, tool calls, thinking, approval) and the AgentChatSession protocol.
Spec: Epic #250 §4.1 (packages), §4.4 (AgentStreamEvent designed close to ACP shape); docs/architecture/dialogue-events.md Appendix A (AgentStreamEvent model).
Scope
Package skeleton
MacApp/Packages/AgentChat/Package.swift:
swift-tools-version: 6.3
.swiftLanguageMode(.v6) on all targets
- Platforms:
.macOS(.v26)
- Targets:
AgentChat (lib) + AgentChatTests
- Dependencies:
TerminalAbstraction (for rawStdout consumption in later tasks), SharedModels, ComposableArchitecture
Public types
Structure under Sources/AgentChat/Model/:
AgentMessage.swift — struct AgentMessage: Identifiable, Equatable, Sendable with id: MessageID (value class wrapping String), role: MessageRole, content: [ContentSegment], createdAt: Date, model: String?, stopReason: StopReason?, usage: TokenUsage?, error: AssistantError?.
MessageRole.swift — .user / .assistant / .system.
ContentSegment.swift — enum: .text(String) (streamed), .thinking(Thinking), .toolCall(ToolCallID) (reference — tool cards live in separate list), .citation(Citation).
ToolCall.swift — struct ToolCall: Identifiable, Equatable, Sendable with id: ToolCallID, name: String, kind: ToolKind (inferred: .read / .edit / .execute / .search / .fetch / .think / .subagent / .other — derived from tool name, aligned with ACP kind), input: JSONValue, status: ToolCallStatus (.pending / .inProgress(startedAt:) / .completed(at:duration:output:) / .failed(at:reason:)), parentMessageID: MessageID, parentToolUseID: ToolCallID? (for subagent nesting).
ApprovalRequest.swift — placeholder for future (MVP uses bypassPermissions; keep type in model for forward-compat).
AgentStreamEvent.swift — sealed enum as in Appendix A of events spec. ACP-compatible shape.
AgentChatStatus.swift — .idle / .streaming / .thinking / .awaitingApproval / .error(Error) / .terminated.
SessionInitInfo.swift, SessionResult.swift, TokenUsage.swift, StopReason.swift, ToolKind.swift, RetryReason.swift, RateLimitInfo.swift, CompactTrigger.swift, JSONValue.swift (minimal local type for opaque tool inputs).
Value classes for IDs
Wrap strings in value class (Swift term for struct with single field + equality):
```swift
public struct MessageID: Hashable, Sendable, Codable { public let rawValue: String }
public struct ToolCallID: Hashable, Sendable, Codable { public let rawValue: String }
public struct SessionID: Hashable, Sendable, Codable { public let rawValue: String }
```
AgentChatSession protocol
```swift
@mainactor
public protocol AgentChatSession: AnyObject, Sendable {
var id: SessionID { get }
var kind: AgentKind { get }
var status: AgentChatStatus { get }
var transcript: AsyncStream { get }
func start() async throws
func send(_ input: AgentInput) async throws
func interrupt() async
func approve(_ request: ApprovalRequest, decision: ApprovalDecision) async throws
func terminate() async
}
public enum AgentInput: Sendable {
case text(String)
case attachment(Data, mimeType: String) // future
}
```
Acceptance Criteria
Relationships
Description
Create the
AgentChatSPM package — pure domain + TCA layer, no SwiftUI, no gRPC, no SwiftTerm. Defines the abstract model for an agent conversation (messages, tool calls, thinking, approval) and theAgentChatSessionprotocol.Spec: Epic #250 §4.1 (packages), §4.4 (AgentStreamEvent designed close to ACP shape); docs/architecture/dialogue-events.md Appendix A (AgentStreamEvent model).
Scope
Package skeleton
MacApp/Packages/AgentChat/Package.swift:swift-tools-version: 6.3.swiftLanguageMode(.v6)on all targets.macOS(.v26)AgentChat(lib) +AgentChatTestsTerminalAbstraction(forrawStdoutconsumption in later tasks),SharedModels,ComposableArchitecturePublic types
Structure under
Sources/AgentChat/Model/:AgentMessage.swift—struct AgentMessage: Identifiable, Equatable, Sendablewithid: MessageID(value class wrappingString),role: MessageRole,content: [ContentSegment],createdAt: Date,model: String?,stopReason: StopReason?,usage: TokenUsage?,error: AssistantError?.MessageRole.swift—.user / .assistant / .system.ContentSegment.swift— enum:.text(String)(streamed),.thinking(Thinking),.toolCall(ToolCallID)(reference — tool cards live in separate list),.citation(Citation).ToolCall.swift—struct ToolCall: Identifiable, Equatable, Sendablewithid: ToolCallID,name: String,kind: ToolKind(inferred:.read / .edit / .execute / .search / .fetch / .think / .subagent / .other— derived from tool name, aligned with ACPkind),input: JSONValue,status: ToolCallStatus(.pending / .inProgress(startedAt:) / .completed(at:duration:output:) / .failed(at:reason:)),parentMessageID: MessageID,parentToolUseID: ToolCallID?(for subagent nesting).ApprovalRequest.swift— placeholder for future (MVP uses bypassPermissions; keep type in model for forward-compat).AgentStreamEvent.swift— sealed enum as in Appendix A of events spec. ACP-compatible shape.AgentChatStatus.swift—.idle / .streaming / .thinking / .awaitingApproval / .error(Error) / .terminated.SessionInitInfo.swift,SessionResult.swift,TokenUsage.swift,StopReason.swift,ToolKind.swift,RetryReason.swift,RateLimitInfo.swift,CompactTrigger.swift,JSONValue.swift(minimal local type for opaque tool inputs).Value classes for IDs
Wrap strings in
value class(Swift term forstructwith single field + equality):```swift
public struct MessageID: Hashable, Sendable, Codable { public let rawValue: String }
public struct ToolCallID: Hashable, Sendable, Codable { public let rawValue: String }
public struct SessionID: Hashable, Sendable, Codable { public let rawValue: String }
```
AgentChatSession protocol
```swift
@mainactor
public protocol AgentChatSession: AnyObject, Sendable {
var id: SessionID { get }
var kind: AgentKind { get }
var status: AgentChatStatus { get }
var transcript: AsyncStream { get }
}
public enum AgentInput: Sendable {
case text(String)
case attachment(Data, mimeType: String) // future
}
```
Acceptance Criteria
AgentChatpackage created withswift-tools-version: 6.3, Swift 6 language mode.AgentMessage,ToolCall,AgentStreamEvent(at least happy path per variant).AgentChatSessionprotocol compiles underSWIFT_STRICT_CONCURRENCY=complete.MessageID,ToolCallID,SessionIDpresent.AgentChatUI,TerminalSwiftTerm,RemoteTerminal— AgentChat is pure domain.swift build,swift testinMacApp/Packages/AgentChat/.Package.swift/Relay.xcodeprojas referenced package (empty consumers allowed; D-10 will consume).Relationships