@@ -63,16 +63,38 @@ final class OpenAICompatibleProvider: AIProvider {
6363 for try await line in bytes. lines {
6464 if Task . isCancelled { break }
6565
66- guard line. hasPrefix ( " data: " ) else { continue }
67- let jsonString = String ( line. dropFirst ( 6 ) )
68- guard jsonString != " [DONE] " else { break }
69-
70- if let text = parseChatCompletionDelta ( jsonString) {
71- continuation. yield ( . text( text) )
72- }
73- if let usage = parseUsageFromChunk ( jsonString) {
74- inputTokens = usage. inputTokens
75- outputTokens = usage. outputTokens
66+ if self . providerType == . ollama {
67+ // Ollama: raw newline-delimited JSON (no SSE "data: " prefix)
68+ guard !line. isEmpty else { continue }
69+ Self . logger. debug ( " Ollama stream line: \( line. prefix ( 200 ) , privacy: . public) " )
70+
71+ if let text = self . parseChatCompletionDelta ( line) {
72+ continuation. yield ( . text( text) )
73+ }
74+ if let usage = self . parseUsageFromChunk ( line) {
75+ inputTokens = usage. inputTokens
76+ outputTokens = usage. outputTokens
77+ }
78+ // Ollama signals completion with "done":true
79+ if let data = line. data ( using: . utf8) ,
80+ let json = try ? JSONSerialization . jsonObject ( with: data) as? [ String : Any ] ,
81+ json [ " done " ] as? Bool == true
82+ {
83+ break
84+ }
85+ } else {
86+ // OpenAI/OpenRouter/Custom: SSE with "data: " prefix
87+ guard line. hasPrefix ( " data: " ) else { continue }
88+ let jsonString = String ( line. dropFirst ( 6 ) )
89+ guard jsonString != " [DONE] " else { break }
90+
91+ if let text = self . parseChatCompletionDelta ( jsonString) {
92+ continuation. yield ( . text( text) )
93+ }
94+ if let usage = self . parseUsageFromChunk ( jsonString) {
95+ inputTokens = usage. inputTokens
96+ outputTokens = usage. outputTokens
97+ }
7698 }
7799 }
78100
0 commit comments