Skip to content

Commit 8dc8946

Browse files
authored
Add Linux job to CI workflow (#13)
* Add Linux job to CI workflow * Bump EventSource to 1.3.0 * Fix syntax error caused by shadowing bytes method * Use EventSource exclusively for SSE handling * Serialize tests that generate locally
1 parent 3ebc30c commit 8dc8946

File tree

7 files changed

+78
-33
lines changed

7 files changed

+78
-33
lines changed

.github/workflows/ci.yml

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,26 @@ on:
66
pull_request:
77
branches: ["main"]
88

9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
913
jobs:
10-
test:
11-
name: Swift ${{ matrix.swift }} on Xcode ${{ matrix.xcode }}
12-
runs-on: ${{ matrix.runs-on }}
14+
test-macos:
15+
name: Swift ${{ matrix.swift }} on macOS ${{ matrix.macos }} with Xcode ${{ matrix.xcode }}
16+
runs-on: macos-${{ matrix.macos }}
1317
env:
1418
DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer"
1519
strategy:
1620
fail-fast: false
1721
matrix:
1822
include:
19-
- swift: "6.1"
23+
- macos: "15"
24+
swift: "6.1"
2025
xcode: "16.3"
21-
runs-on: macos-15
22-
- swift: "6.2"
26+
- macos: "26"
27+
swift: "6.2"
2328
xcode: "26.0"
24-
runs-on: macos-26
2529
timeout-minutes: 10
2630
steps:
2731
- name: Checkout code
@@ -42,3 +46,27 @@ jobs:
4246

4347
- name: Test
4448
run: swift test -v
49+
50+
test-linux:
51+
name: Swift ${{ matrix.swift-version }} on Linux
52+
runs-on: ubuntu-latest
53+
strategy:
54+
fail-fast: false
55+
matrix:
56+
swift-version:
57+
- 6.1.0
58+
timeout-minutes: 10
59+
steps:
60+
- name: Checkout code
61+
uses: actions/checkout@v4
62+
63+
- name: Setup Swift
64+
uses: vapor/[email protected]
65+
with:
66+
toolchain: ${{ matrix.swift-version }}
67+
68+
- name: Build
69+
run: swift build -v
70+
71+
- name: Test
72+
run: swift test -v

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ let package = Package(
3030
dependencies: [
3131
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0"),
3232
.package(url: "https://github.com/mattt/JSONSchema.git", from: "1.3.0"),
33-
.package(url: "https://github.com/mattt/EventSource.git", from: "1.2.0"),
33+
.package(url: "https://github.com/mattt/EventSource.git", from: "1.3.0"),
3434
.package(url: "https://github.com/mattt/PartialJSONDecoder.git", from: "1.0.0"),
3535
.package(url: "https://github.com/ml-explore/mlx-swift-examples/", branch: "main"),
3636
.package(url: "https://github.com/huggingface/swift-transformers", from: "1.0.0"),

Sources/AnyLanguageModel/Extensions/URLSession+Extensions.swift

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -83,37 +83,28 @@ extension URLSession {
8383
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
8484
}
8585

86-
let (bytes, response) = try await bytes(for: request)
86+
let (data, response) = try await self.data(for: request)
8787

8888
guard let httpResponse = response as? HTTPURLResponse else {
8989
throw URLSessionError.invalidResponse
9090
}
9191

9292
guard (200 ..< 300).contains(httpResponse.statusCode) else {
93-
var errorData = Data()
94-
for try await byte in bytes {
95-
errorData.append(byte)
96-
}
97-
98-
if let errorString = String(data: errorData, encoding: .utf8) {
93+
if let errorString = String(data: data, encoding: .utf8) {
9994
throw URLSessionError.httpError(statusCode: httpResponse.statusCode, detail: errorString)
10095
}
10196
throw URLSessionError.httpError(statusCode: httpResponse.statusCode, detail: "Invalid response")
10297
}
10398

104-
var buffer = Data()
105-
106-
for try await byte in bytes {
107-
buffer.append(byte)
99+
var buffer = data
108100

109-
while let newlineIndex = buffer.firstIndex(of: UInt8(ascii: "\n")) {
110-
let chunk = buffer[..<newlineIndex]
111-
buffer = buffer[buffer.index(after: newlineIndex)...]
101+
while let newlineIndex = buffer.firstIndex(of: UInt8(ascii: "\n")) {
102+
let chunk = buffer[..<newlineIndex]
103+
buffer = buffer[buffer.index(after: newlineIndex)...]
112104

113-
if !chunk.isEmpty {
114-
let decoded = try decoder.decode(T.self, from: chunk)
115-
continuation.yield(decoded)
116-
}
105+
if !chunk.isEmpty {
106+
let decoded = try decoder.decode(T.self, from: chunk)
107+
continuation.yield(decoded)
117108
}
118109
}
119110

@@ -151,10 +142,28 @@ extension URLSession {
151142
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
152143
}
153144

154-
let (bytes, _) = try await bytes(for: request)
145+
let (asyncBytes, response) = try await self.data(for: request)
146+
147+
guard let httpResponse = response as? HTTPURLResponse else {
148+
throw URLSessionError.invalidResponse
149+
}
150+
151+
guard (200 ..< 300).contains(httpResponse.statusCode) else {
152+
if let errorString = String(data: asyncBytes, encoding: .utf8) {
153+
throw URLSessionError.httpError(statusCode: httpResponse.statusCode, detail: errorString)
154+
}
155+
throw URLSessionError.httpError(statusCode: httpResponse.statusCode, detail: "Invalid response")
156+
}
157+
155158
let decoder = JSONDecoder()
159+
let parser = EventSource.Parser()
160+
161+
for byte in asyncBytes {
162+
await parser.consume(byte)
163+
}
164+
await parser.finish()
156165

157-
for try await event in bytes.events {
166+
while let event = await parser.getNextEvent() {
158167
guard let data = event.data.data(using: .utf8) else { continue }
159168

160169
if let decoded = try? decoder.decode(T.self, from: data) {

Tests/AnyLanguageModelTests/LlamaLanguageModelTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Testing
66
#if Llama
77
@Suite(
88
"LlamaLanguageModel",
9+
.serialized,
910
.enabled(if: ProcessInfo.processInfo.environment["LLAMA_MODEL_PATH"] != nil)
1011
)
1112
struct LlamaLanguageModelTests {

Tests/AnyLanguageModelTests/OllamaLanguageModelTests.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import Testing
33

44
@testable import AnyLanguageModel
55

6-
@Suite("OllamaLanguageModel", .enabled(if: ProcessInfo.processInfo.environment["CI"] == nil))
6+
@Suite(
7+
"OllamaLanguageModel",
8+
.serialized,
9+
.enabled(if: ProcessInfo.processInfo.environment["CI"] == nil)
10+
)
711
struct OllamaLanguageModelTests {
812
let model = OllamaLanguageModel(model: "qwen3:8b")
913

Tests/AnyLanguageModelTests/SystemLanguageModelTests.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import AnyLanguageModel
1010
}
1111
}()
1212

13-
@Suite("SystemLanguageModel", .enabled(if: isSystemLanguageModelAvailable))
13+
@Suite(
14+
"SystemLanguageModel",
15+
.enabled(if: isSystemLanguageModelAvailable)
16+
)
1417
struct SystemLanguageModelTests {
1518
@available(macOS 26.0, *)
1619
@Test func basicResponse() async throws {

0 commit comments

Comments
 (0)