A Swift SDK for Dify AI that provides a complete interface to the Dify Service API. This SDK follows modern Swift best practices and provides native async/await support, streaming via AsyncSequence, and comprehensive, typed 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. Use remote URLs for any type; local uploads support images out of the box (auto MIME detection) and other formats when you provide an explicit
mimeType(server acceptance may vary by deployment) - 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
- Knowledge Base uses typed options:
KBIndexingTechnique(.economy,.highQuality),KBRetrievalModel.KBSearchMethod(.semanticSearch,.fullTextSearch,.hybridSearch), andKBDocumentForm(.textModel,.hierarchicalModel,.qaModel). Note:docLanguageis required only whendocForm == .qaModeland is omitted otherwise.
- Knowledge Base uses typed options:
- Error Handling: Detailed error types with localized descriptions
- Testing: Comprehensive test suite using the Swift Testing framework (WWDC 2024) with parallel 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
DifyClient– Base HTTP client with shared request/response helpers and streaming plumbingChatClient– Chat messages (blocking/streaming), conversations, variables, feedback, annotations, audio, app infoCompletionClient– Completion messages (blocking/streaming), feedback, files, app info/site/parameters, text-to-audioWorkflowClient– Workflow run (blocking/streaming), run detail and logsKnowledgeBaseClient– Datasets, documents, segments/child chunks, retrieve, embedding models
Key shared components:
Models.swift– All request/response models and typed wrappers (including Knowledge Base typed models)Utilities.swift– JSON coders, multipart builder, helpers, and lightweight debug logging
- 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 {
if let m = event.message {
// incremental text chunks
print(m.answer, terminator: "")
} else if let end = event.messageEnd {
print("\n[done] total tokens: \(end.metadata.usage?.totalTokens ?? 0)")
} else if let e = event.error {
print("\n[error] \(e.code): \(e.message)")
} // handle other kinds (tts_message, ping, etc.) as needed
}Stop an in-progress chat generation by task id:
try await chatClient.stopChatGeneration(taskId: "<task_id>", user: "user_123")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 {
if let m = event.message {
print(m.answer, terminator: "")
} else if let end = event.messageEnd {
print("\n[done] total tokens: \(end.metadata.usage?.totalTokens ?? 0)")
} else if let e = event.error {
print("\n[error] \(e.code): \(e.message)")
}
}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 file. Images have automatic MIME detection; for other file types you can pass mimeType explicitly.
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" // for non-images, pass appropriate MIME (e.g., "text/plain")
)
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 completionStream = try await completionClient.createStreamingCompletionMessage(
inputs: ["query": "Stream a long answer"],
user: "user_123"
)
var taskId: String?
for try await event in completionStream {
if let id = event.message?.taskId { taskId = id }
if /* your condition */ false { break }
}
if let id = taskId {
_ = try await completionClient.stopCompletionMessage(taskId: id, user: "user_123")
}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 {
if let started = event.workflowStarted {
print("Started: \(started.workflowRunId)")
} else if let chunk = event.textChunk {
print("[node \(chunk.name)] \(chunk.status)")
} else if let finished = event.workflowFinished {
print("\nDone: \(finished.data.status)")
}
}
// 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 (typed OpenAPI request)
let fileData = Data() // Your document data
let documentResponse = try await knowledgeBaseClient.createDocumentFromFile(
datasetId: dataset.id,
fileName: "document.pdf",
fileData: fileData,
data: KBCreateDocumentByFileData(
indexingTechnique: .highQuality,
docForm: .textModel,
processRule: KBProcessRule(mode: .automatic)
)
)
// List documents in the dataset
let documents = try await knowledgeBaseClient.listDocuments(datasetId: dataset.id)
for document in documents.data {
print("Document: \(document.name)")
}// Filters when listing datasets
let datasets = try await knowledgeBaseClient.listDatasets(
keyword: "docs",
tagIds: ["t1","t2"],
includeAll: false,
page: 1,
limit: 20
)
// Get and update dataset detail
let detail = try await knowledgeBaseClient.getDatasetDetail(datasetId: dataset.id)
let updated = try await knowledgeBaseClient.updateDataset(
datasetId: detail.id,
KBUpdateDatasetRequest(name: "Renamed KB", indexingTechnique: .economy)
)
// Create document from text
let createdFromText = try await knowledgeBaseClient.createDocumentFromText(
datasetId: dataset.id,
KBCreateDocumentByTextRequest(
name: "intro.txt",
text: "Welcome to our docs!",
indexingTechnique: .highQuality,
docForm: .textModel,
processRule: KBProcessRule(mode: .automatic)
)
)
// Create document from file (multipart + JSON data field)
let createdFromFile = try await knowledgeBaseClient.createDocumentFromFile(
datasetId: dataset.id,
fileName: "guide.pdf",
fileData: Data(/* file bytes */),
data: KBCreateDocumentByFileData(
originalDocumentId: nil,
indexingTechnique: .highQuality,
docForm: .textModel,
processRule: KBProcessRule(mode: .automatic)
)
)
// QA model: requires docLanguage
let createdQADoc = try await knowledgeBaseClient.createDocumentFromText(
datasetId: dataset.id,
KBCreateDocumentByTextRequest(
name: "qa.txt",
text: "Q: ... A: ...",
docForm: .qaModel,
docLanguage: "English", // required for .qaModel
processRule: KBProcessRule(mode: .automatic)
)
)
// Document detail and indexing status
let docDetail = try await knowledgeBaseClient.getDocumentDetail(
datasetId: dataset.id,
documentId: createdFromText.id
)
let statuses = try await knowledgeBaseClient.getDocumentIndexingStatus(
datasetId: dataset.id,
batch: "batch-001"
)
// Segments (chunks)
let segList = try await knowledgeBaseClient.listSegments(datasetId: dataset.id, documentId: createdFromText.id)
let segPage = try await knowledgeBaseClient.createSegments(
datasetId: dataset.id,
documentId: createdFromText.id,
KBCreateSegmentsRequest(segments: [.init(content: "FAQ", answer: nil, keywords: ["faq"])])
)
let segId = segPage.data.first!.id
let segDetail = try await knowledgeBaseClient.getSegmentDetail(datasetId: dataset.id, documentId: createdFromText.id, segmentId: segId)
let segUpdated = try await knowledgeBaseClient.updateSegment(
datasetId: dataset.id,
documentId: createdFromText.id,
segmentId: segId,
KBUpdateSegmentRequest(segment: .init(content: "FAQ updated", answer: nil, keywords: ["faq"], enabled: true, regenerateChildChunks: false))
)
// Child chunks (hierarchical mode)
let childList = try await knowledgeBaseClient.listChildChunks(datasetId: dataset.id, documentId: createdFromText.id, segmentId: segId)
let child = try await knowledgeBaseClient.createChildChunk(datasetId: dataset.id, documentId: createdFromText.id, segmentId: segId, KBCreateChildChunkRequest(content: "Sub-section"))
let childUpdated = try await knowledgeBaseClient.updateChildChunk(datasetId: dataset.id, documentId: createdFromText.id, segmentId: segId, childChunkId: child.data.id, KBUpdateChildChunkRequest(content: "Sub-section v2"))
try await knowledgeBaseClient.deleteChildChunk(datasetId: dataset.id, documentId: createdFromText.id, segmentId: segId, childChunkId: child.data.id)
// Retrieval
let retrieve = try await knowledgeBaseClient.retrieve(
datasetId: dataset.id,
KBRetrieveRequest(query: "What is onboarding?", retrievalModel: nil)
)
// Embedding models (grouped by provider)
let providers = try await knowledgeBaseClient.getAvailableEmbeddingModels()
// Note: Knowledge Base Tag endpoints are intentionally unavailable in this SDK for
// reliable testing across environments.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 ChatClient, CompletionClient, and WorkflowClient)
// ChatClient
let siteFromChat = try await chatClient.getApplicationWebAppSettings()
// CompletionClient
let completionClient = try CompletionClient(apiKey: "your_api_key")
let site = try await completionClient.getApplicationSiteSettings()
// WorkflowClient
let workflowClient = try WorkflowClient(apiKey: "your_api_key")
let wfSite = try await workflowClient.getApplicationWebAppSettings()
print("Title: \(site.title ?? "N/A")")
// Note: Check actual response structure for available propertiesManage 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:
ChatMessageResponseCompletionMessageResponseWorkflowResponseFileUploadResponseDatasetResponseDocumentResponse
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(open string wrapper) supporting:.document,.image,.audio,.video,.customAPIFilewith comprehensive file handling for all supported formats
JSONDecoder.difyDecoder/JSONEncoder.difyEncoderfor consistent date handling and API compatibilityDIFY_SDK_DEBUG=trueprints sanitized request/response debug output and decoder hints for flexible fields (e.g., stringified JSON in workflow run detail)
DifyError.invalidURL()DifyError.invalidResponse()DifyError.noData()DifyError.invalidAPIKeyDifyError.httpError(_:_:)DifyError.networkError(_:)DifyError.decodingError(_:)DifyError.missingDatasetIdDifyError.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
)// Typed rules using the OpenAPI-aligned model only
let typed = KBProcessRule.custom(
rules: KBProcessRules(
preProcessingRules: [
KBPreprocessingRule(id: .removeExtraSpaces, enabled: true),
KBPreprocessingRule(id: .removeUrlsEmails, enabled: true)
],
segmentation: KBSegmentationRule(separator: "\n", maxTokens: 500),
parentMode: .paragraph
)
)
// Use the typed createDocumentFromFile with file data and rules
let fileData = Data("Document content".utf8)
let response = try await knowledgeBaseClient.createDocumentFromFile(
datasetId: "dataset_id",
fileName: "Custom Document.txt",
fileData: fileData,
data: KBCreateDocumentByFileData(
indexingTechnique: .economy,
docForm: .textModel,
processRule: typed
)
)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 Testing display names)
swift test --filter "DifyClient Tests"
swift test --filter "ChatClient Tests"
swift test --filter "CompletionClient Tests"
swift test --filter "WorkflowClient Tests"
swift test --filter "KnowledgeBaseClient Tests"
swift test --filter "Utilities Tests"
swift test --filter "Isolated Mock Session Tests"
# Optional integration suites (disabled by default; require env vars)
swift test --filter "KnowledgeBaseClient Integration"
swift test --filter "WorkflowClient Integration"
# Run tests with verbose output
swift test --verbose
# Run tests sequentially if needed (not recommended)
swift test --no-parallel- Mock-based Unit Tests: Unit tests use mock responses; no real API calls required
- Parallel Test Execution: Isolated mock sessions enable safe parallel runs
- No External Dependencies: Unit tests are offline and deterministic
- Broad Coverage: Tests include endpoints, streaming, error scenarios, and edge cases
- Swift Testing Framework: Uses the modern Swift Testing framework (WWDC 2024)
Tip: Integration suites are opt-in via env vars. Knowledge Base requires DIFY_KB_API_KEY (optional DIFY_BASE_URL); Workflow requires DIFY_WORKFLOW_API_KEY (optional DIFY_BASE_URL, optional DIFY_WORKFLOW_INPUTS_JSON).
The repository includes a live, networked integration suite for WorkflowClient. It is disabled unless a workflow app key is provided.
Requirements:
DIFY_WORKFLOW_API_KEY— an App API key for a published Workflow application- Optional
DIFY_BASE_URL— defaults tohttps://api.dify.ai/v1(must include/v1)
Run the suite:
DIFY_WORKFLOW_API_KEY=your_key \
swift test --filter "WorkflowClient Integration" --no-parallelHow inputs are provided:
- Inputs are derived automatically from
/parameters(user_input_form). The tests fill required fields using defaults or sensible placeholders and respect select options when present. - Optional override: set
DIFY_WORKFLOW_INPUTS_JSONto a JSON object (e.g.,{"query":"Hello"}) to provide explicit inputs for your workflow. This is not required but can be useful for apps with custom validation.
Notes on workflow models and streaming:
- Early streaming events (e.g.,
workflow_started,node_started) may omit some metrics on certain deployments. The SDK models are tolerant for these events while remaining strict for terminal results. getWorkflowRunDetailacceptsinputs/outputsreturned either as JSON objects or as stringified JSON.- Enable debug logging with
DIFY_SDK_DEBUG=trueto see sanitized request/response snapshots and decode hints during integration runs.
- 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
Additional note on streaming: on Linux the SDK uses a buffered fallback (URLSession.data(for:)) for SSE parsing instead of URLSession.bytes; event semantics remain the same.
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