feat(cli-bridge): Use oneOf with discriminator for ServerMessage in OpenAPI spec

Resolved 💬 2 comments Opened Jan 1, 2026 by wiseyoda Closed Jan 1, 2026

Summary

The iOS client (CodingBridge) uses OpenAPI code generation to stay in sync with cli-bridge types. With v0.4.1, WebSocket message schemas were added to the OpenAPI spec, which is great! However, ServerMessage uses anyOf instead of oneOf with a discriminator, which prevents proper Swift enum generation.

Current Behavior

ServerMessage in the OpenAPI spec uses anyOf:

{
  "ServerMessage": {
    "anyOf": [
      { "$ref": "#/components/schemas/ConnectedMessage" },
      { "$ref": "#/components/schemas/StreamServerMessage" },
      { "$ref": "#/components/schemas/PermissionRequestMessage" },
      // ... more types
    ]
  }
}

This generates a flat Swift struct with all properties from all message types:

// Generated (incorrect)
public struct ServerMessage: Codable {
    public var type: ModelType
    public var agentId: String      // from ConnectedMessage
    public var sessionId: UUID      // from ConnectedMessage
    public var tool: String         // from PermissionRequestMessage
    public var input: [String: JSONValue]  // from PermissionRequestMessage
    // ... 30+ properties merged together
}

Expected Behavior

ServerMessage should use oneOf with a discriminator (like StreamMessage and ClientMessage already do):

{
  "ServerMessage": {
    "oneOf": [
      { "$ref": "#/components/schemas/ConnectedMessage" },
      { "$ref": "#/components/schemas/StreamServerMessage" },
      { "$ref": "#/components/schemas/PermissionRequestMessage" },
      // ... more types
    ],
    "discriminator": {
      "propertyName": "type",
      "mapping": {
        "connected": "#/components/schemas/ConnectedMessage",
        "stream": "#/components/schemas/StreamServerMessage",
        "permission": "#/components/schemas/PermissionRequestMessage"
        // ... more mappings
      }
    }
  }
}

This would generate a proper Swift enum with associated values:

// Generated (correct)
public enum ServerMessage: Codable {
    case typeConnectedMessage(ConnectedMessage)
    case typeStreamServerMessage(StreamServerMessage)
    case typePermissionRequestMessage(PermissionRequestMessage)
    // ...
}

Working Examples in Current Spec

These already use oneOf with discriminator and generate correctly:

  • ClientMessage
  • StreamMessage
  • ContentBlock

Impact

With this change, CodingBridge can be 100% generated from the OpenAPI spec, eliminating hand-written CLIServerMessage type and ensuring the iOS client stays perfectly in sync with the backend protocol.

Files to Update

  • src/hono/schemas/websocket.ts - Change ServerMessage from z.union() to use discriminated union pattern

Related

  • cli-bridge v0.4.1 added WebSocket schemas to OpenAPI
  • CodingBridge uses openapi-generator swift6 for code generation

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗