---
title: ajevans99/swift-openai
framework: Swift Package Catalog
role: article
path: packages/ajevans99/swift-openai
---

# ajevans99/swift-openai

> [!IMPORTANT]

## Library Structure

| Layer | Package | Description | Use Case | |-------|---------|-------------|----------| | 🚀 1 — High-level API | **`OpenAIKit`** | Opinionated wrapper for conversational agents using tools, including the `Tool` protocol, `ResponseSession` actor, error policies, and streaming support. | Build assistants, chatbots, and AI-powered features with minimal setup. | | 🧠 2 — Swifty OpenAI Client | **`OpenAICore`** | Lightweight async wrapper around the OpenAI API with ergonomic Swift types and methods like `createResponse(...)`. Built on top of `OpenAICore`. | Interact directly with OpenAI endpoints in a clean, modern Swift style. | | 🛠️ 3 — Low-level Generated Layer | **`OpenAIFoundation`** | Fully autogenerated models and API client from the OpenAPI spec. No opinions, no wrappers—just raw access. | Advanced integrations, custom wrappers, or OpenAPI-driven workflows. |

> 💡 All layers rely on a pluggable transport layer conforming to [`ClientTransport` protocol](https://swiftpackageindex.com/apple/swift-openapi-runtime/main/documentation/openapiruntime/clienttransport). You can use [`OpenAPIAsyncHTTPClient`](https://github.com/swift-server/swift-openapi-async-http-client), [`OpenAPIURLSession`](https://github.com/apple/swift-openapi-urlsession), or bring your own.

## **`OpenAIKit`**

1. Define your tools, conforming to the `Tool` protocol.

2. Register your tools

3. Setup a `ResponseSession`

4. Start streaming (or send async request)

```swift let orchestrator = ToolOrchestratorPlugin(   tools: [WeatherTool(apiKey: "...")] )

let handle = try await session.stream(   "What's the weather in SF?",   plugins: TextPlugin(), orchestrator )

let (textChannel, toolChannel) = handle.pluginEvents

try await withThrowingTaskGroup(of: Void.self) { group in   group.addTask {     for try await event in textChannel.events {       switch event {       case .delta(let chunk): print(chunk, terminator: "")       case .completed: print()       }     }   }

group.addTask {     for try await event in toolChannel.events {       print(event)     }   }

try await group.waitForAll() } ```

<details> <summary><strong>Plugin System Deep Dive</strong></summary>

### Why this design?

`ResponseSession.stream` gives you two layers at once:

- `handle.raw`: full `StreamingResponse` protocol events. - `handle.pluginEvents`: strongly typed plugin channels tailored to your app.

This lets you keep low-level access when needed, while still writing most app logic against clean domain events.

### Compose any number of plugins

Plugins are variadic, so you are not limited to 1-3:

```swift let handle = try await session.stream(   "Generate an image and explain it",   plugins: TextPlugin(), ToolOrchestratorPlugin(), ImagePlugin(), MyPlugin() )

let (text, tools, images, custom) = handle.pluginEvents ```

### Author your own plugin

```swift struct RefusalPlugin: ResponseStreamPlugin {   enum Event: Sendable {     case refusal(String)   }

func consume(     _ event: StreamingResponse,     context: inout StreamPluginContext   ) async throws -> Event? {     guard case .outputItem(.done(let item, _)) = event else { return nil }     guard case .message(let message) = item else { return nil }

for content in message.content {       if case .refusal(let refusal) = content {         return .refusal(refusal.refusal)       }     }     return nil   } } ```

### Tool orchestration

`ToolOrchestratorPlugin` supports plugin-local tools:

```swift let orchestrator = ToolOrchestratorPlugin(   tools: [WeatherTool(apiKey: "...")],   errorPolicy: .returnAsMessage ) ```

If a tool is not found locally, it can fall back to session-level registration (`session.register(tool:)`) for compatibility. When set, the orchestrator's `errorPolicy` override is also applied on that fallback path.

### Lossless streams and cancellation

Semantic channels are unbounded and lossless by default, so text deltas are never silently truncated. Consume every enabled channel concurrently, or disable unused raw capture:

```swift let handle = try await session.stream(   "Coach me through the next step",   streamOptions: .init(rawEvents: .disabled),   plugins: ResponseLifecyclePlugin(), TextPlugin(), ToolOrchestratorPlugin() )

// Explicitly cancels the provider task, HTTP body, and all channels. handle.cancel() ```

For bounded memory, select `.bounded(256)`; overflow fails explicitly instead of dropping data.

`ResponseLifecyclePlugin` exposes created, completed, incomplete, and failed responses with their IDs. Full stateless conversation history can be submitted with `stream(inputItems:)` or `send(inputItems:)`, including assistant-role `EasyInputMessage` values.

### Raw-only mode

If you want protocol events only:

```swift let raw = try await session.streamRaw("Debug this turn") for try await event in raw {   print(event.value) } ```

</details>

## **`OpenAIFoundation`**

| Endpoint | Supported? | | --- | --- | | `/responses` | [x] | | `/images` | [x] |

## Examples

Checkout the [Example CLI Project](Example) for some more sample usages.

## Code Generation

> [!NOTE] > This section is only relevant for library maintainers. If you're just using the package, you can skip this.

`swift-openai` uses `swift-openapi-generator` to generate models and endpoint definitions directly from OpenAI’s documented OpenAPI spec at [openapi.documented.yml](https://app.stainless.com/api/spec/documented/openai/openapi.documented.yml). This ensures maximum compatibility and future-proofing as the spec evolves.

To ensure generated code remains buildable against fast-moving upstream changes, generation runs two deterministic patch phases:

1. OpenAPI spec transforms in `Scripts/apply-patches.sh` (pre-generation). 2. Generated source transforms in `Scripts/apply-generated-patches.sh` (post-generation).

| Task                                    | Command          | |-----------------------------------------|------------------| | To check for spec changes               | `make check`     | | To fetch the latest `openapi.yaml`      | `make fetch`     | | To apply the necessary transforms       | `make patches`   | | To generate the Swift types             | `make generate`  | | To fetch, patch, and generate           | `make all`       |

## Snapshot Tests

The test suite includes fixture-based response decode snapshots and an opt-in live recorder.

| Task                                                     | Command                 | |----------------------------------------------------------|-------------------------| | Run all tests (includes snapshot replay)                | `make test`             | | Run a live decode smoke test (requires `OPENAI_API_KEY`) | `make test-live-snapshots` | | Record/update snapshot fixtures from live API            | `make record-snapshots` |

## Package Metadata

Repository: ajevans99/swift-openai

Default branch: main

README: README.md
