Swift SDK

Swift SDK for inference shell agents and apps.

The Swift SDK runs apps and chats with agents from iOS, macOS, and server-side Swift. It uses Swift concurrency (async/await, AsyncThrowingStream), and its Codable models are generated from the API's own types, the same source as the JavaScript and Python SDKs.


Installation

Add the package with Swift Package Manager. In Package.swift:

swift
1dependencies: [2    .package(url: "https://github.com/inference-sh/sdk-swift", from: "0.1.0"),3],4targets: [5    .target(name: "MyApp", dependencies: [6        .product(name: "InferenceSDK", package: "sdk-swift"),7    ]),8]

In Xcode: File → Add Package Dependencies… and paste https://github.com/inference-sh/sdk-swift.

swift
1import InferenceSDK

Requirements

  • Swift 5.9+
  • iOS 17+ / macOS 14+
  • Linux (Foundation + FoundationNetworking; the library uses no Apple-only frameworks)

Source: github.com/inference-sh/sdk-swift


API Key

Get a key from settings → workspace → api keys and pass it to the client:

swift
1import Foundation2import InferenceSDK34let client = InferenceClient(apiKey: "inf_your_key")5// Or from the environment:6// let client = InferenceClient(apiKey: ProcessInfo.processInfo.environment["INFERENCE_API_KEY"] ?? "")

Do not ship a raw API key inside an app binary. For a public app, call the API from your own backend (or proxy it) and hand the client a short-lived key.

→ Authentication


Client API

InferenceClient exposes namespaced APIs. Responses are unwrapped from the { "data", "messages" } envelope and errors are RFC 9457 problem details.

PropertyPurpose
client.tasksrun, create, get, list, cancel, delete; getLogs, getTimings, and getTelemetry for observability; visibility
client.filesupload, list, get, delete
client.agentslist, get, getByName, create, update, versions, duplicate, visibility, A2A card, run interrupts
client.chatslist, get, update, delete, getStatus, stop, cancelMessage, stream
client.appslist, get, getByName, create, update, versions, visibility, licenses
client.searchsearch and suggest
AgentChatSessionStateful agent chat (see Agent SDK)

Every request and response model (TaskDTO, ChatMessageDTO, AgentDTO, …) is generated from the API's Go types. Enums tolerate values the API adds later: an unknown status decodes instead of failing.

client.uploadFile() remains available as the older form of client.files.upload().

For features not listed above, use the REST API or see the SDK overview — client structure parity table.


Running Apps

Basic Usage

swift
1import InferenceSDK23let client = InferenceClient(apiKey: "inf_your_key")45// Run and wait for completion (default)6let task = try await client.tasks.run(ApiAppRunRequest(7    app: "infsh/flux-schnell",8    input: ["prompt": "a lighthouse at dawn, watercolor"]9))1011print(task.output)

run returns the finished TaskDTO. A failed task throws TaskRunError.failed, a cancelled one TaskRunError.cancelled.

input and setup are JSONValue, which accepts Swift literals directly: strings, numbers, booleans, arrays, dictionaries, and nil.

Setup Parameters

Setup parameters configure the app instance (for example, model selection). Workers with matching setup are "warm" and skip setup:

swift
1let task = try await client.tasks.run(ApiAppRunRequest(2    app: "my-app",3    setup: ["model": "schnell"],4    input: ["prompt": "hello"]5))

With Progress Updates

run follows the task's stream by default and reports every change:

swift
1let task = try await client.tasks.run(2    ApiAppRunRequest(app: "my-app", input: ["prompt": "hello"]),3    options: TaskRunOptions(4        onUpdate: { task in print("status:", task.status.rawValue) },5        onDelta: { delta, seq in print("delta \(seq):", delta) }6    )7)

Pass stream: false in TaskRunOptions to poll the task status instead (pollIntervalMs, default 2000).

→ Streaming · Polling

Fire and Forget

swift
1let task = try await client.tasks.run(2    ApiAppRunRequest(app: "my-app", input: ["prompt": "hello"]),3    options: TaskRunOptions(wait: false)4)5print("started", task.id)67// later8let current = try await client.tasks.get(task.id)

Task Management

swift
1try await client.tasks.cancel(taskId)2let logs = try await client.tasks.getLogs(taskId)3let page = try await client.tasks.list(CursorListRequest(limit: 20))

→ Tasks REST API · Observability


Agent SDK

Chat with AI agents programmatically.

Chat Sessions

AgentChatSession is the full agent chat state machine. It creates the chat on the first message, follows the chat's server-sent event stream, applies token deltas to the right message, reconnects, and pages older messages. It is a port of the JavaScript SDK's agent actions and reducer, and has no UI framework dependency, so it can drive SwiftUI, UIKit, AppKit, or a CLI.

swift
1import InferenceSDK23@MainActor4func chat() async {5    let session = AgentChatSession(client: client, agent: "my-team/support-agent@latest")67    session.onChange = { state in8        // Every state transition: messages, chat status, connection, errors.9        if let last = state.messages.last { print(last.role.rawValue, last.text) }10    }11    session.callbacks.onTurnEnd = { chat in print("agent finished") }1213    await session.sendMessage("What can you help me with?")14}

AgentChatSession is @MainActor. Its state is an AgentChatState:

FieldDescription
messages[ChatMessageDTO], in order. message.text joins the text blocks; content holds reasoning, images, files, and errors
chatThe ChatDTO, including busy state and queued messages
connectionStatus.idle, .connecting, .streaming, or .error
errorThe last error, if any. clearError() resets it
hasOlderMessagesMore history to page in with loadOlderMessages()

Sending while the agent is still working queues the message on the server; it runs when the current turn ends. cancelMessage(_:) removes a queued message.

SwiftUI

Mirror the state into an ObservableObject (or @Observable) and render from it:

swift
1@MainActor2final class ChatModel: ObservableObject {3    @Published private(set) var state = AgentChatState.initial4    let session: AgentChatSession56    init(client: InferenceClient, agent: String) {7        session = AgentChatSession(client: client, agent: agent)8        session.onChange = { [weak self] in self?.state = $0 }9    }1011    func send(_ text: String) { Task { await session.sendMessage(text) } }12    func stop() { session.stopGeneration() }13}

Tools, Approvals, and Interrupts

When a tool call needs the user, its invocation (message.toolInvocations) waits in awaitingApproval or awaitingInput:

swift
1try await session.approveTool(invocation.id)2try await session.rejectTool(invocation.id, reason: "not now")3try await session.alwaysAllowTool(invocation.id, toolName: invocation.function.name)45// Client-side tools and widget forms6try await session.submitToolResult(invocation.id, result: #"{"form_data":{"size":"large"}}"#)78// Run-level interrupts (client.agents.listRunInterrupts(runId))9try await session.resolveInterrupt(interruptId, decision: "allow")   // or "deny"

Tools that render UI (A2UI widgets) carry it on invocation.widget or in the result. Widget.parse(string:) reads every widget format the web app accepts.

→ Client Tools · Agents REST API — Interrupt gates

Resuming a Chat

swift
1session.setChatId(existingChatId)   // loads the chat and its latest messages, then streams2let more = await session.loadOlderMessages()

Streaming Responses

Without a session, runAgentStream posts a message and streams snapshots of the assistant reply until it finishes:

swift
1let request = ApiAgentRunRequest(agent: "my-team/support-agent@latest", input: LLMInput(text: "hello"))2for try await message in client.runAgentStream(request) {3    print(message.status.rawValue, message.text)4    if message.status.isTerminal { break }5}

Each element is a full ChatMessageDTO snapshot, not a delta.

Chat Management

swift
1let agent = try await client.agents.getByName(namespace: "my-team", name: "support-agent")2let versions = try await client.agents.listVersions(agent.id)34let chats = try await client.chats.list(CursorListRequest(limit: 20))5try await client.chats.stop(chatId)6for try await event in client.chats.stream(chatId) {7    switch event {8    case .message(let message, _): print(message.text)9    case .chat(let chat): print("chat", chat.status.rawValue)10    case .delta(let messageId, let delta): print(messageId, delta)11    case .run: break12    }13}

Speech

TextToSpeech and SpeechToText wrap any speech app on inference shell. They read the app's input and output schema, so the same code works across providers:

swift
1let stt = SpeechToText(client: client, app: "elevenlabs/stt")2let text = try await stt.transcribe(wavData)34let tts = TextToSpeech(client: client, app: "infsh/kokoro-tts")5for try await audio in tts.synthesize(reply) {   // long text is chunked; one Data per chunk6    try player.enqueue(audio)7}

Error Handling

HTTP failures throw InferenceError.http(status:body:) with the API's RFC 9457 problem details in body. localizedDescription uses detail, then title, and falls back to the raw body (for example HTTP 402: Insufficient balance). Failed and cancelled tasks throw TaskRunError.

swift
1do {2    let task = try await client.tasks.run(request)3} catch let error as InferenceError {4    // .http(status:body:) carries the API's problem details5    print(error.localizedDescription)6} catch let error as TaskRunError {7    print("task failed:", error.localizedDescription)8}

→ REST overview — response format — problem+json shape


Configuration

swift
1let client = InferenceClient(2    baseURL: URL(string: "https://api.inference.sh")!,  // default3    apiKey: key,4    onMessage: { notices in print(notices) }            // server warnings attached to responses5)

InferenceClient is a Sendable value type. Create one and share it.


File Upload

Uploads go straight to storage through a presigned URL. Pass the returned uri as app input:

swift
1let file = try await client.files.upload(imageData, filename: "photo.jpg", contentType: "image/jpeg")23let task = try await client.tasks.run(ApiAppRunRequest(4    app: "my-image-app",5    input: ["image": .string(file.uri)]6))

App outputs that are files come back as URLs. TaskResultDTO.fileURL(_:) reads either shape apps use (a bare URL or {"uri": ...}), and client.download(_:) fetches the bytes.

→ Files


REST API (curl)

For simple integrations, you can use the REST API directly with streaming:

bash
1# Single call with streaming2curl -N -X POST \3  -H "Authorization: Bearer YOUR_API_KEY" \4  -H "Content-Type: application/json" \5  -d '{6    "agent": "my-org/assistant@abc123",7    "input": {"text": "Hello!", "role": "user"},8    "stream": true9  }' \10  https://api.inference.sh/agents/run

The -N flag disables curl's buffering so you see events in real-time.


What's Next?

we use cookies

we use cookies to ensure you get the best experience on our website. for more information on how we use cookies, please see our cookie policy.

by clicking "accept", you agree to our use of cookies.
learn more.