A Swift SDK for Dify AI that provides a complete interface to the Dify Service API. This SDK follows Swift best practices and provides native async/await support with comprehensive error handling.
- Complete API Coverage: Supports all Dify API endpoints including chat, completion, workflows, knowledge base management, application info, feedbacks, and annotations
- Enhanced File Support: Full support for documents, images, audio, video, and custom file types via remote URLs; local upload endpoint currently supports images only (png/jpg/jpeg/webp/gif)
- Modern Swift: Built with Swift 6.1+ using modern concurrency (async/await) and follows Swift best practices
- Cross-Platform: Works on macOS, iOS, tvOS, watchOS, and Linux
- Advanced Streaming: Built-in streaming response handling for real-time interactions including workflow events
- Type Safety: Comprehensive Swift types for all API request/response models with proper snake_case to camelCase conversion
- Error Handling: Detailed error types with localized descriptions
- Testing: Full test coverage using the latest Swift Testing framework (WWDC2024) with parallel test execution support
- Application Management: Complete application info, parameters, metadata, and configuration support
- Feedback & Annotations: Full support for message feedback with content and annotation management
- Conversation Variables: Extract and manage structured data from conversations
- Swift 6.1+
- macOS 13.0+ / iOS 16.0+ / tvOS 16.0+ / watchOS 9.0+
- Linux (with Swift 6.1+) - See Platform Support for details
Add the following to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/nedithgar/dify-swift-client.git", from: "1.0.0")
]
Or add it through Xcode:
- File → Add Package Dependencies
- Enter:
https://github.com/nedithgar/dify-swift-client.git
import DifySwiftClient
// Initialize the client with your API key
let client = try DifyClient(apiKey: "your_api_key_here")
let chatClient = try ChatClient(apiKey: "your_api_key")
// Send a chat message
let response = try await chatClient.createChatMessage(
inputs: [:],
query: "Hello, how can you help me?",
user: "user_123"
)
print("Response: \(response.answer)")
let streamingResponse = try await chatClient.createStreamingChatMessage(
inputs: [:],
query: "Tell me a story",
user: "user_123"
)
for try await event in streamingResponse {
switch event {
case .message(let m):
// incremental text chunks
print(m.answer, terminator: "")
case .messageEnd(let end):
print("\n[done] total tokens: \(end.metadata.usage?.totalTokens ?? 0)")
case .error(let e):
print("\n[error] \(e.code): \(e.message)")
default:
// handle other events as needed (tts_message, ping, etc.)
break
}
}
let completionClient = try CompletionClient(apiKey: "your_api_key")
// Send a completion request
let completion = try await completionClient.createCompletionMessage(
inputs: ["query": "Write a haiku about Swift"],
user: "user_123"
)
print("Completion: \(completion.answer)")
// Streaming completion
let completionStream = try await completionClient.createStreamingCompletionMessage(
inputs: ["query": "List 3 facts about the macOS kernel"],
user: "user_123"
)
for try await event in completionStream {
switch event {
case .message(let m):
print(m.answer, terminator: "")
case .messageEnd(let end):
print("\n[done] total tokens: \(end.metadata.usage?.totalTokens ?? 0)")
case .error(let e):
print("\n[error] \(e.code): \(e.message)")
default: break
}
}
The SDK now supports comprehensive file handling for all Dify-supported formats:
// Document files: TXT, MD, PDF, DOCX, XLSX, etc. (use remote URL)
let documentFile = APIFile(
type: .document,
transferMethod: .remoteUrl,
url: "https://example.com/document.pdf"
)
// Audio files: MP3, WAV, M4A, etc.
let audioFile = APIFile(
type: .audio,
transferMethod: .remoteUrl,
url: "https://example.com/audio.mp3"
)
// Video files: MP4, MOV, etc. (use remote URL)
let videoFile = APIFile(
type: .video,
transferMethod: .remoteUrl,
url: "https://example.com/sample.mp4"
)
// Custom file types (use remote URL)
let customFile = APIFile(
type: .custom,
transferMethod: .remoteUrl,
url: "https://example.com/other.xyz"
)
// Using remote image URL
let imageFile = APIFile(
type: .image,
transferMethod: .remoteUrl,
url: "https://example.com/image.jpg"
)
let response = try await chatClient.createChatMessage(
inputs: [:],
query: "Analyze these files and provide insights",
user: "user_123",
files: [documentFile, audioFile, imageFile]
)
Upload and use a local image file (upload endpoint currently supports images only):
let completionClient = try CompletionClient(apiKey: "your_api_key")
let fileData = Data() // Your image data
let uploadResponse = try await completionClient.uploadFile(
fileData: fileData,
fileName: "picture.png",
user: "user_123",
mimeType: "image/png"
)
let localFiles = [APIFile(
type: .image,
transferMethod: .localFile,
uploadFileId: uploadResponse.id
)]
let responseWithLocalFile = try await chatClient.createChatMessage(
inputs: [:],
query: "Describe this image",
user: "user_123",
files: localFiles
)
You can optionally include workflowId and traceId in both blocking and streaming chat requests:
let blocking = try await chatClient.createChatMessage(
inputs: [:],
query: "Hello",
user: "user_123",
workflowId: "wf_123",
traceId: "trace_abc"
)
let stream = try await chatClient.createStreamingChatMessage(
inputs: [:],
query: "Tell me a story",
user: "user_123",
workflowId: "wf_123",
traceId: "trace_def"
)
You can preview or download previously uploaded files by file ID:
let data = try await completionClient.previewFile(fileId: uploadResponse.id)
// Force download (server will set Content-Disposition to attachment)
let downloadData = try await completionClient.previewFile(fileId: uploadResponse.id, asAttachment: true)
// Optionally write to disk
try downloadData.write(to: URL(fileURLWithPath: "/tmp/downloaded.png"))
You can also preview files via ChatClient
:
let chatClient = try ChatClient(apiKey: "your_api_key")
let bytes = try await chatClient.previewFile(fileId: uploadResponse.id, asAttachment: true)
let workflowClient = try WorkflowClient(apiKey: "your_api_key")
// Run the default workflow (blocking)
let workflowResponse = try await workflowClient.runWorkflow(
inputs: ["input_key": "input_value"],
user: "user_123"
)
print("Workflow status: \(workflowResponse.data.status)")
print("Outputs: \(String(describing: workflowResponse.data.outputs))")
// Run a specific workflow version by ID (blocking)
let responseById = try await workflowClient.runWorkflow(
workflowId: "wf_123",
inputs: ["topic": "swift"],
user: "user_123",
files: [
APIFile(type: .document, transferMethod: .remoteUrl, url: "https://example.com/doc.pdf")
],
traceId: "trace_abc"
)
// Streaming run (default workflow)
let stream = try await workflowClient.runStreamingWorkflow(
inputs: ["query": "analyze"],
user: "user_123",
traceId: "trace_xyz"
)
for try await event in stream {
switch event {
case .workflowStarted(let e):
print("Started: \(e.workflowRunId)")
case .textChunk(let t):
print(t.data.text, terminator: "")
case .workflowFinished(let e):
print("\nDone: \(e.data.status)")
default: break
}
}
// Streaming run for a specific workflow id
let streamById = try await workflowClient.runStreamingWorkflow(
workflowId: "wf_123",
inputs: ["query": "hello"],
user: "user_123"
)
// Get workflow run detail (use workflowRunId)
let detail = try await workflowClient.getWorkflowRunDetail(
workflowRunId: workflowResponse.workflowRunId
)
// Create a new knowledge base client
let knowledgeBaseClient = try KnowledgeBaseClient(apiKey: "your_api_key")
// Create a new dataset
let dataset = try await knowledgeBaseClient.createDataset(name: "My Knowledge Base")
// Create document by uploading a file
let fileData = Data() // Your document data
let processRule = ProcessRule(
mode: "automatic",
rules: [
"remove_extra_spaces": "true",
"segmentation_separator": "\n",
"max_tokens": "500"
]
)
let documentResponse = try await knowledgeBaseClient.createDocument(
datasetId: dataset.id,
fileData: fileData,
fileName: "document.pdf",
processRule: processRule
)
// List documents in the dataset
let documents = try await knowledgeBaseClient.listDocuments(datasetId: dataset.id)
for document in documents.data {
print("Document: \(document.name)")
}
let chatClient = try ChatClient(apiKey: "your_api_key")
// Get conversations
let conversations = try await chatClient.getConversations(user: "user_123")
for conversation in conversations.data {
print("Conversation: \(conversation.name)")
}
// Get messages from a conversation
let messages = try await chatClient.getConversationMessages(
conversationId: "conversation_id",
user: "user_123"
)
// Rename a conversation
try await chatClient.renameConversation(
conversationId: "conversation_id",
name: "New Conversation Name",
autoGenerate: false,
user: "user_123"
)
// Delete a conversation
try await chatClient.deleteConversation(
conversationId: "conversation_id",
user: "user_123"
)
do {
let response = try await chatClient.createChatMessage(
inputs: [:],
query: "Hello",
user: "user_123"
)
print("Success: \(response.answer)")
} catch let error as DifyError {
// DifyError is a struct; inspect fields for details
let status = error.status.map(String.init) ?? "N/A"
let code = error.code ?? "N/A"
let message = error.message ?? error.localizedDescription
print("Dify error [status=\(status), code=\(code)]: \(message)")
} catch {
print("Unexpected error: \(error)")
}
let chatClient = try ChatClient(apiKey: "your_api_key")
// Convert text to audio (available in ChatClient and CompletionClient)
let audioResponse = try await chatClient.textToAudio(
text: "Hello, this is a test message",
user: "user_123"
)
// Convert audio to text
let audioData = Data() // Your audio file data
let textResponse = try await chatClient.audioToText(
audioFile: audioData,
user: "user_123"
)
// Alternatively, using CompletionClient for text-to-audio
let completionClient = try CompletionClient(apiKey: "your_api_key")
let ttsData = try await completionClient.getTextToAudio(
text: "Hello from completion app",
user: "user_123"
)
Get comprehensive application details and configuration:
// Get basic application information (available in multiple clients)
let chatClient = try ChatClient(apiKey: "your_api_key")
let appInfo = try await chatClient.getApplicationInfo()
print("App: \(appInfo.name) - \(appInfo.description)")
print("Mode: \(appInfo.mode), Author: \(appInfo.authorName)")
// Get application meta information (available in ChatClient)
let meta = try await chatClient.getApplicationMeta()
// Note: Check actual response structure for available properties
// Get site/webapp settings (available in CompletionClient)
let completionClient = try CompletionClient(apiKey: "your_api_key")
let site = try await completionClient.getApplicationSiteSettings()
print("Title: \(site.title ?? "N/A")")
// Note: Check actual response structure for available properties
Manage feedback and annotations with rich content support:
let chatClient = try ChatClient(apiKey: "your_api_key")
// Get application feedbacks (available in ChatClient)
let feedbacks = try await chatClient.getApplicationFeedbacks(page: 1, limit: 20)
for feedback in feedbacks.data {
print("Feedback: \(feedback.rating) - \(feedback.content)")
}
// Manage annotations (available in ChatClient)
let annotations = try await chatClient.getAnnotations(page: 1, limit: 20)
print("Total annotations: \(annotations.total)")
// Create new annotation
let newAnnotation = try await chatClient.createAnnotation(
question: "What is artificial intelligence?",
answer: "AI is a branch of computer science focused on creating systems that can perform tasks typically requiring human intelligence."
)
// Configure annotation reply settings
let settingsResponse = try await chatClient.configureAnnotationReply(
action: "enable",
embeddingModelProvider: "openai",
embeddingModel: "text-embedding-ada-002",
scoreThreshold: 0.8
)
Extract and manage structured data from conversations:
// Get conversation variables
let variables = try await chatClient.getConversationVariables(
conversationId: "conv_123",
user: "user_123",
limit: 50
)
for variable in variables.data {
print("Variable: \(variable.name) (\(variable.valueType))")
print("Value: \(variable.value.value)")
print("Description: \(variable.description)")
}
// Update variable value (supports string/number/object)
let updated = try await chatClient.updateConversationVariable(
conversationId: "conv_123",
variableId: "var_123",
value: AnyCodable(["enabled": true]),
user: "user_123"
)
// Filter variables by name
let filtered = try await chatClient.getConversationVariables(
conversationId: "conv_123",
user: "user_123",
variableName: "user_name"
)
Utilize advanced chat capabilities:
// Create chat message with auto-generation control
let response = try await chatClient.createChatMessage(
inputs: ["context": "customer support"],
query: "I need help with my order",
user: "user_123",
conversationId: nil,
files: nil,
autoGenerateName: false, // Disable automatic title generation
workflowId: "wf_123",
traceId: "trace_abc"
)
// Get workflow logs (for workflow-enabled apps)
let workflowLogs = try await workflowClient.getWorkflowLogs(
keyword: "error",
status: "failed",
page: 1,
limit: 10
)
for log in workflowLogs.data {
print("Workflow: \(log.workflowRun.id)")
print("Status: \(log.workflowRun.status)")
print("Duration: \(log.workflowRun.elapsedTime)s")
if let error = log.workflowRun.error {
print("Error: \(error)")
}
}
DifyClient
: Base client with common functionalityChatClient
: Chat-based interactions and conversation managementCompletionClient
: Completion-based interactionsWorkflowClient
: Workflow execution and managementKnowledgeBaseClient
: Knowledge base and document management
All API responses are strongly typed with Swift structs and include comprehensive support for the latest Dify API features. The SDK uses custom AnyCodable
for flexible JSON handling and proper snake_case to camelCase conversion:
ChatMessageResponse
CompletionMessageResponse
WorkflowResponse
FileUploadResponse
DatasetResponse
DocumentResponse
ApplicationInfoResponse
- Basic app informationApplicationParametersResponse
- Detailed app parameters and configurationApplicationMetaResponse
- Application metadata and tool iconsApplicationSiteResponse
- Site/webapp settings
MessageFeedbackRequest
- Feedback with content supportApplicationFeedbacksResponse
- Application feedback listingsAnnotationsListResponse
- Annotation managementAnnotationReplyJobResponse
- Annotation configurationAnnotationReplyJobStatusResponse
- Annotation job tracking
ConversationVariablesResponse
- Structured conversation dataWorkflowLogsResponse
- Workflow execution logs and history
FileType
enum supporting:.document
,.image
,.audio
,.video
,.custom
APIFile
with comprehensive file handling for all supported formats
DifyError.invalidURL()
DifyError.invalidAPIKey
DifyError.httpError(_:_:)
DifyError.networkError(_:)
DifyError.decodingError(_:)
DifyError.missingDatasetId
DifyError.fileNotFound(_:)
let client = try DifyClient(
apiKey: "your_api_key",
baseURL: "https://your-custom-dify-instance.com/v1"
)
let customSession = URLSession(configuration: .default)
let client = try DifyClient(
apiKey: "your_api_key",
session: customSession
)
let processRule = ProcessRule(
mode: "custom",
rules: [
"remove_extra_spaces": "true",
"remove_urls_emails": "true",
"segmentation_separator": "\n",
"max_tokens": "500"
]
)
// Use createDocument method with file data
let fileData = Data("Document content".utf8)
let response = try await knowledgeBaseClient.createDocument(
datasetId: "dataset_id",
fileData: fileData,
fileName: "Custom Document.txt",
processRule: processRule
)
The SDK includes comprehensive tests using Swift Testing framework with enhanced parallel execution support:
# Run all tests (parallel execution by default)
swift test
# Run specific test suites
swift test --filter "DifyClientTests"
swift test --filter "ChatClientTests"
swift test --filter "CompletionClientTests"
swift test --filter "WorkflowClientTests"
swift test --filter "KnowledgeBaseClientTests"
swift test --filter "ModelsTests"
swift test --filter "UtilitiesTests"
# Run tests with verbose output
swift test --verbose
# Run tests sequentially if needed (not recommended)
swift test --no-parallel
- 100% Mock-based Testing: All tests use mock responses with no real API calls required
- Parallel Test Execution: Tests use isolated mock sessions for thread-safe parallel execution
- No External Dependencies: Tests run offline and are completely deterministic
- Comprehensive Coverage: Includes unit tests for all API endpoints, streaming responses, error scenarios, and edge cases
- Swift Testing Framework: Built with the modern Swift Testing framework introduced at WWDC 2024
- macOS 13.0+
- iOS 16.0+
- tvOS 16.0+
- watchOS 9.0+
The SDK compiles and runs on Linux with Swift 6.1+. However, please note:
- Successful compilation on Linux
- API compatibility analysis
- Community feedback and issue reports
Linux users are encouraged to report any compatibility issues they encounter. While we cannot guarantee the same level of testing coverage as Darwin platforms, we are committed to addressing Linux-specific issues as they arise.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
For issues and questions:
- Create an issue on GitHub
- Check the Dify documentation
- Built based on the official Dify Python SDK
- Follows Swift best practices and modern concurrency patterns