Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions scripts/fix_format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ echo "Running swift-format..."
if command -v swift-format >/dev/null 2>&1; then
if [ "$CHECK_ONLY" = true ]; then
echo "Linting Swift files..."
swift-format lint -r Package.swift swift/core
swift-format lint -r Package.swift swift/
else
echo "Formatting Swift files..."
swift-format format -i -r Package.swift swift/core
swift-format format -i -r Package.swift swift/
fi
else
echo "Warning: swift-format command not found. Skipping Swift formatting."
Expand Down
39 changes: 36 additions & 3 deletions swift/core/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# A2UI Swift Core Agent Guide (AGENTS.md)

This document is the authoritative guide for AI agents operating within the
`swift/` directory tree. It outlines target boundaries, coding conventions, and
verification protocols.
`swift/` directory tree. It outlines target boundaries, coding conventions, spec
compliance rules, and verification protocols.

---

Expand Down Expand Up @@ -58,7 +58,40 @@ Key rules:

---

## 3. Verification Protocol
## 3. Spec Compliance \u0026 Source-of-Truth Hierarchy

When implementing or modifying any wire-format types (message envelopes, action
payloads, error types), JSON Pointer semantics, or data model behavior, agents
**MUST** verify consistency against the authoritative sources in this order:

1. **JSON Schemas** (`specification/v0_9_1/json/`) — the primary authority for
wire-format field names, required properties, and structural shape.
2. **Core SDK Blueprint** (`blueprints/modules/a2ui_core.blueprint.md`) — defines
cross-language behavioral rules (e.g., JSON Pointer Implementation Rules,
auto-vivification, sparse arrays, notification strategy).
3. **`web_core` Reference** (`renderers/web_core/src/v0_9/`) — the canonical
TypeScript implementation to cross-check behavioral semantics (e.g., how
`undefined`/`nil` is handled for arrays vs objects, auto-vivification
conditions, root replacement rules).

Common pitfalls to avoid:

- **Wire payload shape**: Do not mirror component property schemas (e.g.,
`Action.event`/`Action.functionCall`) in message envelopes. Message payloads
have their own flat schemas (e.g., `action.name`, `action.surfaceId`).
- **Version fields**: All server-to-client and client-to-server messages require
a top-level `version` field. Always encode it and validate it on decode.
- **Array deletion vs sparse**: Setting an array index to `nil`/`undefined`
must preserve array length (set to `.null`), not remove the element. Object
keys are removed entirely.
- **Root replacement**: The root JSON value can be replaced with any valid
`JSONValue` type, not just `.object`.
- **Auto-vivification**: Arrays should be vivified for any numeric path segment
(`Int(key) != nil`), not just index `"0"`.

---

## 4. Verification Protocol

Before completing any task, agents **MUST** execute:

Expand Down
3 changes: 3 additions & 0 deletions swift/core/CODING_STANDARDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ Writing excellent tests ensures high confidence in our SDKs and services.
- Deterministic object encoding (e.g., keys sorted alphabetically)
- Round-trip string serialization/deserialization
- Order-independent equality checks for dictionaries or objects
- **No force unwraps:** Never use force unwraps (`!`) in tests. Use
`try #require(...)` from Swift Testing to safely unwrap optionals, which
produces a clear test failure instead of a crash.

- **Running Tests:** Use the `run_tests.sh` script to run tests on the root package:
```bash
Expand Down
43 changes: 43 additions & 0 deletions swift/core/Sources/A2UICore/Errors/ClientServerError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// Encapsulates all client-to-server error types.
public enum ClientServerError: Equatable, Codable, Sendable {
case validationFailed(ValidationFailedError)
case generic(GenericError)

private struct Discriminator: Decodable {
let code: String
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let discriminator = try container.decode(Discriminator.self)
if discriminator.code == ValidationFailedError.errorCode {
self = .validationFailed(try container.decode(ValidationFailedError.self))
} else {
self = .generic(try container.decode(GenericError.self))
}
}
Comment thread
pinieb marked this conversation as resolved.

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .validationFailed(let validation):
try container.encode(validation)
case .generic(let generic):
try container.encode(generic)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import A2UIJSON
import Foundation
import JSONSchema
import OrderedJSON
/// Represents a generic processing or runtime client error.
public struct GenericError: Error, Equatable, Codable, Sendable {
public let code: String
public let surfaceID: String
public let message: String

// A2UICore — Stateful runtime engine implementing the A2UI state machine,
// validation pipeline, and bidirectional event routing.
// This file is intentionally a placeholder; real types will be added in
// subsequent PRs.
private enum CodingKeys: String, CodingKey {
case code
case surfaceID = "surfaceId"
case message
}

public init(code: String, surfaceID: String, message: String) {
self.code = code
self.surfaceID = surfaceID
self.message = message
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import A2UICore
import Testing

struct A2UICoreTests {
@Test func placeholderTest() {
#expect(Bool(true))
}
/// Errors that occur during local function evaluation.
public enum LocalFunctionError: Error, Sendable {
case functionNotFound(String)
case missingArgument(String)
case invalidArgumentType(expected: String, actual: String)
}
51 changes: 51 additions & 0 deletions swift/core/Sources/A2UICore/Errors/ValidationFailedError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// Represents a structured validation failure.
public struct ValidationFailedError: Error, Equatable, Codable, Sendable {
public static let errorCode = "VALIDATION_FAILED"

public let code: String = errorCode
public let surfaceID: String
public let path: String
public let message: String

private enum CodingKeys: String, CodingKey {
case code
case surfaceID = "surfaceId"
case path
case message
}

public init(surfaceID: String, path: String, message: String) {
self.surfaceID = surfaceID
self.path = path
self.message = message
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let decodedCode = try container.decode(String.self, forKey: .code)
guard decodedCode == Self.errorCode else {
throw DecodingError.dataCorruptedError(
forKey: .code,
in: container,
debugDescription: "Invalid error code: \(decodedCode)"
)
}
surfaceID = try container.decode(String.self, forKey: .surfaceID)
path = try container.decode(String.self, forKey: .path)
message = try container.decode(String.self, forKey: .message)
}
}
Loading
Loading