Skip to content

Commit e40ecab

Browse files
authored
Merge pull request #355 from MacPaw/347-openai-openapi-code-generation-issue
2 parents d6b6271 + f87d40b commit e40ecab

File tree

12 files changed

+6677
-2269
lines changed

12 files changed

+6677
-2269
lines changed

Demo/DemoChat/Sources/ResponsesStore.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ public final class ResponsesStore: ObservableObject {
292292
chatMessage: message
293293
)
294294
)
295+
case .webSearchToolCall/*(let webSearchToolCall)*/:
296+
// see where output_item.done is handled for handling tool calls
297+
break
298+
case .functionToolCall(let functionToolCall):
299+
// see where output_item.done is handled for handling tool calls
300+
break
295301
default:
296302
throw StoreError.unhandledOutputItem(output)
297303
}
@@ -340,6 +346,13 @@ public final class ResponsesStore: ObservableObject {
340346
)
341347
case .outputText(let outputTextEvent):
342348
try handleOutputTextEvent(outputTextEvent)
349+
case .outputTextAnnotation(let outputTextAnnotationEvent):
350+
switch outputTextAnnotationEvent {
351+
case .added/*(let added)*/:
352+
// TODO: ResponseStreamEvent.Annotation have become OpenAPIObjectContainer for some reason, needs update
353+
// applyOutputTextAnnotationDeltaToMessageBeingStreamed(messageId: added.itemId, addedAnnotation: added.annotation)
354+
break
355+
}
343356
case .contentPart(.done(let contentPartDoneEvent)):
344357
try updateMessageBeingStreamed(
345358
messageId: contentPartDoneEvent.itemId,
@@ -416,11 +429,6 @@ public final class ResponsesStore: ObservableObject {
416429
messageId: responseTextDeltaEvent.itemId,
417430
newText: responseTextDeltaEvent.delta
418431
)
419-
case .annotationAdded(let annotationDeltaEvent):
420-
try applyOutputTextAnnotationDeltaToMessageBeingStreamed(
421-
messageId: annotationDeltaEvent.itemId,
422-
addedAnnotation: annotationDeltaEvent.annotation
423-
)
424432
case .done(let responseTextDoneEvent):
425433
if messageBeingStreamed?.text != responseTextDoneEvent.text {
426434
throw StoreError.incompleteLocalTextOnOutputTextDoneEvent(

Demo/DemoChat/Sources/UI/ResponsesChatDetailView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ public struct ResponsesChatDetailView: View {
140140
functionCallingEnabled: settingsStore.functionCallingEnabled
141141
)
142142
} catch {
143+
print("store.send error: \(error)")
143144
errorTitle = error.localizedDescription
144145
errorAlertPresented = true
145146
}

Sources/OpenAI/Private/Extensions for generated code/Schemas+Description.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ extension Components.Schemas.ResponseTextDeltaEvent: CustomStringConvertible {
3838
}
3939
}
4040

41-
extension Components.Schemas.ResponseTextAnnotationDeltaEvent: CustomStringConvertible {
41+
extension Components.Schemas.ResponseOutputTextAnnotationAddedEvent: CustomStringConvertible {
4242
public var description: String {
43-
"ResponseTextAnnotationDeltaEvent(itemId: \"\(itemId)\", outputIndex: \(outputIndex), contentIndex: \(contentIndex), annotationIndex: \(annotationIndex), annotation: \(annotation))"
43+
"ResponseOutputTextAnnotationAddedEvent(itemId: \"\(itemId)\", outputIndex: \(outputIndex), contentIndex: \(contentIndex), annotationIndex: \(annotationIndex), annotation: \(annotation))"
4444
}
4545
}
4646

Sources/OpenAI/Private/Streaming/ModelResponseEventsStreamInterpreter.swift

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,22 @@ final class ModelResponseEventsStreamInterpreter: @unchecked Sendable, StreamInt
5555
}
5656

5757
private func processEvent(_ event: ServerSentEventsStreamParser.Event) throws {
58-
guard let modelResponseEventType = ModelResponseStreamEventType(rawValue: event.eventType) else {
59-
throw InterpreterError.unknownEventType(event.eventType)
58+
var finalEvent = event
59+
if event.eventType == "response.output_text.annotation.added" {
60+
// Remove when they have fixed (unified)!
61+
//
62+
// By looking at [API Reference](https://platform.openai.com/docs/api-reference/responses-streaming/response/output_text_annotation/added)
63+
// and generated type `Schemas.ResponseOutputTextAnnotationAddedEvent`
64+
// We can see that "output_text.annotation" is incorrect, whereas output_text_annotation is the correct one
65+
let fixedDataString = event.decodedData.replacingOccurrences(of: "response.output_text.annotation.added", with: "response.output_text_annotation.added")
66+
finalEvent = .init(id: event.id, data: fixedDataString.data(using: .utf8) ?? event.data, decodedData: fixedDataString, eventType: "response.output_text_annotation.added", retry: event.retry)
6067
}
6168

62-
let responseStreamEvent = try responseStreamEvent(modelResponseEventType: modelResponseEventType, data: event.data)
69+
guard let modelResponseEventType = ModelResponseStreamEventType(rawValue: finalEvent.eventType) else {
70+
throw InterpreterError.unknownEventType(finalEvent.eventType)
71+
}
72+
73+
let responseStreamEvent = try responseStreamEvent(modelResponseEventType: modelResponseEventType, data: finalEvent.data)
6374
onEventDispatched?(responseStreamEvent)
6475
}
6576

@@ -75,37 +86,35 @@ final class ModelResponseEventsStreamInterpreter: @unchecked Sendable, StreamInt
7586
) throws -> ResponseStreamEvent {
7687
switch modelResponseEventType {
7788
case .responseCreated:
78-
.created(try decoder.decode(ResponseEvent.self, from: data))
89+
.created(try decode(data: data))
7990
case .responseInProgress:
80-
.inProgress(try decoder.decode(ResponseEvent.self, from: data))
91+
.inProgress(try decode(data: data))
8192
case .responseCompleted:
82-
.completed(try decoder.decode(ResponseEvent.self, from: data))
93+
.completed(try decode(data: data))
8394
case .responseFailed:
84-
.failed(try decoder.decode(ResponseEvent.self, from: data))
95+
.failed(try decode(data: data))
8596
case .responseIncomplete:
86-
.incomplete(try decoder.decode(ResponseEvent.self, from: data))
97+
.incomplete(try decode(data: data))
8798
case .responseOutputItemAdded:
88-
.outputItem(.added(try decoder.decode(ResponseOutputItemAddedEvent.self, from: data)))
99+
.outputItem(.added(try decode(data: data)))
89100
case .responseOutputItemDone:
90-
.outputItem(.done(try decoder.decode(ResponseOutputItemDoneEvent.self, from: data)))
101+
.outputItem(.done(try decode(data: data)))
91102
case .responseContentPartAdded:
92-
.contentPart(.added(try decoder.decode(Schemas.ResponseContentPartAddedEvent.self, from: data)))
103+
.contentPart(.added(try decode(data: data)))
93104
case .responseContentPartDone:
94-
.contentPart(.done(try decoder.decode(Schemas.ResponseContentPartDoneEvent.self, from: data)))
105+
.contentPart(.done(try decode(data: data)))
95106
case .responseOutputTextDelta:
96-
.outputText(.delta(try decoder.decode(Schemas.ResponseTextDeltaEvent.self, from: data)))
97-
case .responseOutputTextAnnotationAdded:
98-
.outputText(.annotationAdded(try decoder.decode(Schemas.ResponseTextAnnotationDeltaEvent.self, from: data)))
107+
.outputText(.delta(try decode(data: data)))
99108
case .responseOutputTextDone:
100-
.outputText(.done(try decoder.decode(Schemas.ResponseTextDoneEvent.self, from: data)))
109+
.outputText(.done(try decode(data: data)))
101110
case .responseRefusalDelta:
102-
.refusal(.delta(try decoder.decode(Schemas.ResponseRefusalDeltaEvent.self, from: data)))
111+
.refusal(.delta(try decode(data: data)))
103112
case .responseRefusalDone:
104-
.refusal(.done(try decoder.decode(Schemas.ResponseRefusalDoneEvent.self, from: data)))
113+
.refusal(.done(try decode(data: data)))
105114
case .responseFunctionCallArgumentsDelta:
106-
.functionCallArguments(.delta(try decoder.decode(Schemas.ResponseFunctionCallArgumentsDeltaEvent.self, from: data)))
115+
.functionCallArguments(.delta(try decode(data: data)))
107116
case .responseFunctionCallArgumentsDone:
108-
.functionCallArguments(.done(try decoder.decode(Schemas.ResponseFunctionCallArgumentsDoneEvent.self, from: data)))
117+
.functionCallArguments(.done(try decode(data: data)))
109118
case .responseFileSearchCallInProgress:
110119
.fileSearchCall(.inProgress(try decode(data: data)))
111120
case .responseFileSearchCallSearching:
@@ -118,7 +127,53 @@ final class ModelResponseEventsStreamInterpreter: @unchecked Sendable, StreamInt
118127
.webSearchCall(.searching(try decode(data: data)))
119128
case .responseWebSearchCallCompleted:
120129
.webSearchCall(.completed(try decode(data: data)))
130+
case .responseReasoningSummaryPartAdded:
131+
.reasoningSummaryPart(.added(try decode(data: data)))
132+
case .responseReasoningSummaryPartDone:
133+
.reasoningSummaryPart(.done(try decode(data: data)))
134+
case .responseReasoningSummaryTextDelta:
135+
.reasoningSummaryText(.delta(try decode(data: data)))
136+
case .responseReasoningSummaryTextDone:
137+
.reasoningSummaryText(.done(try decode(data: data)))
138+
case .responseImageGenerationCallCompleted:
139+
.imageGenerationCall(.completed(try decode(data: data)))
140+
case .responseImageGenerationCallGenerating:
141+
.imageGenerationCall(.generating(try decode(data: data)))
142+
case .responseImageGenerationCallInProgress:
143+
.imageGenerationCall(.inProgress(try decode(data: data)))
144+
case .responseImageGenerationCallPartialImage:
145+
.imageGenerationCall(.partialImage(try decode(data: data)))
146+
case .responseMcpCallArgumentsDelta:
147+
.mcpCall(.arguments(.delta(try decode(data: data))))
148+
case .responseMcpCallArgumentsDone:
149+
.mcpCall(.arguments(.done(try decode(data: data))))
150+
case .responseMcpCallCompleted:
151+
.mcpCall(.completed(try decode(data: data)))
152+
case .responseMcpCallFailed:
153+
.mcpCall(.failed(try decode(data: data)))
154+
case .responseMcpCallInProgress:
155+
.mcpCall(.inProgress(try decode(data: data)))
156+
case .responseMcpListToolsCompleted:
157+
.mcpListTools(.completed(try decode(data: data)))
158+
case .responseMcpListToolsFailed:
159+
.mcpListTools(.failed(try decode(data: data)))
160+
case .responseMcpListToolsInProgress:
161+
.mcpListTools(.inProgress(try decode(data: data)))
162+
case .responseQueued:
163+
.queued(try decode(data: data))
164+
case .responseReasoningDelta:
165+
.reasoning(.delta(try decode(data: data)))
166+
case .responseReasoningDone:
167+
.reasoning(.done(try decode(data: data)))
168+
case .responseReasoningSummaryDelta:
169+
.reasoningSummary(.delta(try decode(data: data)))
170+
case .responseReasoningSummaryDone:
171+
.reasoningSummary(.done(try decode(data: data)))
172+
case .responseOutputTextAnnotationAdded:
173+
.outputTextAnnotation(.added(try decode(data: data)))
121174
case .responseAudioDelta:
175+
// Audio, AudioTranscript and CodeInterpreter events are not part of API Reference at the moment
176+
// But they are present in code generated from OpenAPI spec, so we also include it
122177
.audio(.delta(try decode(data: data)))
123178
case .responseAudioDone:
124179
.audio(.done(try decode(data: data)))

Sources/OpenAI/Private/Streaming/ModelResponseStreamEventType.swift

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ enum ModelResponseStreamEventType: String {
1313
case responseCompleted = "response.completed"
1414
case responseFailed = "response.failed"
1515
case responseIncomplete = "response.incomplete"
16+
case responseQueued = "response.queued"
1617

1718
case responseOutputItemAdded = "response.output_item.added"
1819
case responseOutputItemDone = "response.output_item.done"
@@ -21,7 +22,6 @@ enum ModelResponseStreamEventType: String {
2122
case responseContentPartDone = "response.content_part.done"
2223

2324
case responseOutputTextDelta = "response.output_text.delta"
24-
case responseOutputTextAnnotationAdded = "response.output_text.annotation.added"
2525
case responseOutputTextDone = "response.output_text.done"
2626

2727
case responseRefusalDelta = "response.refusal.delta"
@@ -38,17 +38,45 @@ enum ModelResponseStreamEventType: String {
3838
case responseWebSearchCallSearching = "response.web_search_call.searching"
3939
case responseWebSearchCallCompleted = "response.web_search_call.completed"
4040

41+
case responseReasoningSummaryPartAdded = "response.reasoning_summary_part.added"
42+
case responseReasoningSummaryPartDone = "response.reasoning_summary_part.done"
43+
44+
case responseReasoningSummaryTextDelta = "response.reasoning_summary_text.delta"
45+
case responseReasoningSummaryTextDone = "response.reasoning_summary_text.done"
46+
47+
case responseImageGenerationCallCompleted = "response.image_generation_call.completed"
48+
case responseImageGenerationCallGenerating = "response.image_generation_call.generating"
49+
case responseImageGenerationCallInProgress = "response.image_generation_call.in_progress"
50+
case responseImageGenerationCallPartialImage = "response.image_generation_call.partial_image"
51+
52+
case responseMcpCallArgumentsDelta = "response.mcp_call.arguments.delta"
53+
case responseMcpCallArgumentsDone = "response.mcp_call.arguments.done"
54+
case responseMcpCallCompleted = "response.mcp_call.completed"
55+
case responseMcpCallFailed = "response.mcp_call.failed"
56+
case responseMcpCallInProgress = "response.mcp_call.in_progress"
57+
58+
case responseMcpListToolsCompleted = "response.mcp_list_tools.completed"
59+
case responseMcpListToolsFailed = "response.mcp_list_tools.failed"
60+
case responseMcpListToolsInProgress = "response.mcp_list_tools.in_progress"
61+
62+
case responseOutputTextAnnotationAdded = "response.output_text_annotation.added"
63+
64+
case responseReasoningDelta = "response.reasoning.delta"
65+
case responseReasoningDone = "response.reasoning.done"
66+
67+
case responseReasoningSummaryDelta = "response.reasoning_summary.delta"
68+
case responseReasoningSummaryDone = "response.reasoning_summary.done"
69+
70+
case error = "error"
71+
72+
// The following events are not present in the API Reference at the moment, but they are in generated code, so we also include them just in case
4173
case responseAudioDelta = "response.audio.delta"
4274
case responseAudioDone = "response.audio.done"
43-
4475
case responseAudioTranscriptDelta = "response.audio_transcript.delta"
4576
case responseAudioTranscriptDone = "response.audio_transcript.done"
46-
4777
case responseCodeInterpreterCallCodeDelta = "response.code_interpreter_call.code.delta"
4878
case responseCodeInterpreterCallCodeDone = "response.code_interpreter_call.code.done"
4979
case responseCodeInterpreterCallInProgress = "response.code_interpreter_call.in_progress"
5080
case responseCodeInterpreterCallInterpreting = "response.code_interpreter_call.interpreting"
5181
case responseCodeInterpreterCallCompleted = "response.code_interpreter_call.completed"
52-
53-
case error = "error"
5482
}

0 commit comments

Comments
 (0)