Skip to content

Commit c5f10e9

Browse files
committed
Fix OpenAI Interceptor, and make OpenAI specific fixes
1 parent af91b66 commit c5f10e9

File tree

21 files changed

+475
-484
lines changed

21 files changed

+475
-484
lines changed

.editorconfig

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ ij_formatter_off_tag = @formatter:off
1313
ij_formatter_on_tag = @formatter:on
1414
ij_formatter_tags_enabled = true
1515
ij_smart_tabs = false
16-
ij_visual_guides =
16+
ij_visual_guides = 120
1717
ij_wrap_on_typing = false
1818

1919
[{*.kt,*.kts}]

build.gradle.kts

-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import com.diffplug.gradle.spotless.SpotlessExtension
21
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
32
import org.gradle.jvm.tasks.Jar
43
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
@@ -9,20 +8,6 @@ plugins {
98
alias(libs.plugins.multiplatform) apply false
109
alias(libs.plugins.assert)
1110
alias(libs.plugins.publish)
12-
alias(libs.plugins.spotless)
13-
}
14-
15-
configure<SpotlessExtension> {
16-
kotlin {
17-
target("**/*.kt")
18-
ktfmt().kotlinlangStyle().configure {
19-
it.setBlockIndent(2)
20-
it.setContinuationIndent(2)
21-
it.setRemoveUnusedImport(true)
22-
}
23-
trimTrailingWhitespace()
24-
endWithNewline()
25-
}
2611
}
2712

2813
@Suppress("OPT_IN_USAGE")

example/src/commonMain/kotlin/Main.kt

-5
This file was deleted.

example/src/commonMain/kotlin/StreamingOps.kt

+14-11
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
1-
package com.xebia.functional.openai
1+
package io.github.nomisrev.openapi
22

33
import io.ktor.client.statement.HttpResponse
44
import io.ktor.client.statement.bodyAsChannel
55
import io.ktor.utils.io.ByteReadChannel
66
import io.ktor.utils.io.readUTF8Line
77
import kotlinx.coroutines.flow.FlowCollector
8+
import kotlinx.serialization.Serializable
89
import kotlinx.serialization.json.Json
10+
import kotlinx.serialization.json.JsonElement
911
import kotlinx.serialization.json.JsonObject
1012
import kotlinx.serialization.serializer
1113

14+
@Serializable
15+
data class ServerSentEvent(
16+
val event: String? = null,
17+
val data: JsonElement? = null,
18+
)
1219

1320
// ServerSentEvent, INTERNAL TO THIS MODULE
1421
// RunDelta | CreateChatCompletionStreamResponse
1522
internal suspend inline fun <reified A> FlowCollector<A>.streamEvents(
1623
response: HttpResponse,
17-
json: Json,
18-
prefix: String,
19-
end: String
24+
json: Json = Json,
25+
prefix: String = "data:",
26+
end: String = "data: [DONE]"
2027
) {
2128
val channel: ByteReadChannel = response.bodyAsChannel()
2229
var nextEvent: String? = null
@@ -29,18 +36,14 @@ internal suspend inline fun <reified A> FlowCollector<A>.streamEvents(
2936
}
3037

3138
// if the line is an event like "event: thread.created" we want to ensure
32-
// A is of type ServerSentEvent and we skip the line treating next `prefix` as a JsonObject
39+
// A is of type ServerSentEvent, and we skip the line treating next `prefix` as a JsonObject
3340
// otherwise we treat the line as a json object if it starts with the prefix
3441
// and emit the value
35-
36-
// if the line is an event like "event: thread.created" we want to ensure
37-
// A is of type ServerSentEvent and we skip the line treating next `prefix` as a JsonObject
3842
if (line.startsWith("event:")) {
3943
nextEvent = line.removePrefix("event:").trim()
4044
continue
41-
}
42-
// otherwise we treat the line as a json object if it starts with the prefix
43-
else if (line.startsWith(prefix) && nextEvent == null) {
45+
} else if (line.startsWith(prefix) && nextEvent == null) {
46+
// otherwise we treat the line as a json object if it starts with the prefix
4447
val data = line.removePrefix(prefix).trim()
4548
val value: A = json.decodeFromString(serializer(), data)
4649
emit(value)

generation/build.gradle.kts

+10-47
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,19 @@
1-
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
2-
31
plugins {
4-
id(libs.plugins.multiplatform.get().pluginId)
2+
id(libs.plugins.jvm.get().pluginId)
53
alias(libs.plugins.serialization)
6-
alias(libs.plugins.assert)
74
id(libs.plugins.publish.get().pluginId)
5+
// Failing on Interceptors.kt
6+
// alias(libs.plugins.spotless)
87
}
98

109
kotlin {
11-
jvm {
12-
@OptIn(ExperimentalKotlinGradlePluginApi::class)
13-
mainRun { mainClass.set("io.github.nomisrev.openapi.MainKt") }
14-
}
15-
// macosArm64()
16-
// linuxX64()
17-
18-
sourceSets {
19-
commonMain {
20-
kotlin.srcDir(project.file("build/generated/openapi/src/commonMain/kotlin"))
21-
22-
dependencies {
23-
api(libs.kasechange)
24-
api(libs.okio)
25-
implementation("io.ktor:ktor-client-core:2.3.6")
26-
api(projects.parser)
27-
}
28-
}
29-
commonTest {
30-
dependencies {
31-
implementation(libs.test)
32-
}
33-
}
34-
jvmMain {
35-
dependencies {
36-
implementation("com.squareup:kotlinpoet:1.17.0")
37-
}
38-
}
39-
// jsMain {
40-
// dependencies {
41-
// implementation("com.squareup.okio:okio-nodefilesystem:3.9.0")
42-
// }
43-
// }
44-
commonTest {
45-
dependencies {
46-
implementation(libs.okio.fakefilesystem)
47-
}
48-
}
49-
}
10+
compilerOptions.freeCompilerArgs.add("-Xcontext-receivers")
5011
}
5112

52-
task("runMacosArm64") {
53-
dependsOn("linkDebugExecutableMacosArm64")
54-
dependsOn("runDebugExecutableMacosArm64")
55-
group = "run"
13+
dependencies {
14+
implementation(libs.okio)
15+
api(libs.ktor.client)
16+
api(projects.typed)
17+
api(libs.kasechange)
18+
api(libs.kotlinpoet)
5619
}

generation/src/main/kotlin/io/github/nomisrev/openapi/APIs.kt

+45-58
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,25 @@ private fun TypeSpec.Builder.apiConstructor(): TypeSpec.Builder =
101101
.build()
102102
)
103103

104+
context(OpenAPIContext)
104105
private fun Route.nestedTypes(): List<TypeSpec> = inputs() + returns() + bodies()
105106

107+
context(OpenAPIContext)
106108
private fun Route.inputs(): List<TypeSpec> =
107-
input.mapNotNull { (it.type as? Resolved.Value)?.value?.toTypeSpec() }
109+
input.mapNotNull { (it.type as? Resolved.Value)?.value?.toTypeSpecOrNull() }
108110

111+
context(OpenAPIContext)
109112
private fun Route.returns(): List<TypeSpec> =
110-
returnType.types.values.mapNotNull { (it.type as? Resolved.Value)?.value?.toTypeSpec() }
113+
returnType.types.values.mapNotNull { (it.type as? Resolved.Value)?.value?.toTypeSpecOrNull() }
111114

115+
context(OpenAPIContext)
112116
private fun Route.bodies(): List<TypeSpec> =
113117
body.types.values.flatMap { body ->
114118
when (body) {
115119
is Route.Body.Json.Defined ->
116-
listOfNotNull((body.type as? Resolved.Value)?.value?.toTypeSpec())
120+
listOfNotNull((body.type as? Resolved.Value)?.value?.toTypeSpecOrNull())
117121

118-
is Route.Body.Multipart.Value -> body.parameters.mapNotNull { it.type.value.toTypeSpec() }
122+
is Route.Body.Multipart.Value -> body.parameters.mapNotNull { it.type.value.toTypeSpecOrNull() }
119123
is Route.Body.Multipart.Ref,
120124
is Route.Body.Xml,
121125
is Route.Body.Json.FreeForm,
@@ -198,85 +202,67 @@ private fun Route.toFun(implemented: Boolean): FunSpec =
198202
if (implemented) {
199203
addCode(
200204
CodeBlock.builder()
201-
.addStatement(
202-
"val response = client.%M {",
203-
MemberName("io.ktor.client.request", "request", isExtension = true)
204-
)
205+
.addStatement("val response = client.%M {", request)
205206
.withIndent {
206207
addStatement("configure()")
207-
addStatement("method = %T.%L", ClassName("io.ktor.http", "HttpMethod"), method.value)
208-
val replace =
209-
input
210-
.mapNotNull {
211-
if (it.input == Parameter.Input.Path)
212-
".replace(\"{${it.name}}\", ${toParamName(Named(it.name))})"
213-
else null
214-
}
215-
.joinToString(separator = "")
216-
addStatement(
217-
"url { %M(%S$replace) }",
218-
MemberName("io.ktor.http", "path", isExtension = true),
219-
path
220-
)
221-
addContentType(body)
222-
addBody(body)
208+
addStatement("method = %T.%L", HttpMethod, method.name())
209+
addPathAndContent()
210+
addBody()
223211
}
224212
.addStatement("}")
225-
.addStatement(
226-
"return response.%M()",
227-
MemberName("io.ktor.client.call", "body", isExtension = true)
228-
)
213+
.addStatement("return response.%M()", MemberName("io.ktor.client.call", "body", isExtension = true))
229214
.build()
230215
)
231216
}
232217
}
233218
.build()
234219

235-
context(OpenAPIContext)
236-
fun CodeBlock.Builder.addContentType(bodies: Route.Bodies): CodeBlock.Builder =
220+
context(OpenAPIContext, CodeBlock.Builder)
221+
fun Route.addPathAndContent() {
222+
val replace = input
223+
.mapNotNull {
224+
if (it.input == Parameter.Input.Path)
225+
".replace(\"{${it.name}}\", ${toParamName(Named(it.name))})"
226+
else null
227+
}
228+
.joinToString(separator = "")
229+
addStatement(
230+
"url { %M(%S$replace) }",
231+
MemberName("io.ktor.http", "path", isExtension = true),
232+
path
233+
)
234+
addContentType(body)
235+
}
236+
237+
context(OpenAPIContext, CodeBlock.Builder)
238+
fun addContentType(bodies: Route.Bodies) {
237239
bodies.firstNotNullOfOrNull { (_, body) ->
238240
when (body) {
239241
is Route.Body.Json -> addStatement(
240-
"%M(%T.%L)",
241-
MemberName("io.ktor.http", "contentType"),
242-
ClassName("io.ktor.http", "ContentType", "Application"),
243-
"Json"
242+
"%M(%T.%L)", contentType, ContentType.nested("Application"), "Json"
244243
)
245244

246245
is Route.Body.Xml -> TODO("Xml input body not supported yet.")
247246
is Route.Body.OctetStream -> TODO("OctetStream input body not supported yet.")
248247
is Route.Body.Multipart.Ref,
249-
is Route.Body.Multipart.Value -> addStatement(
250-
"%M(%T.%L)",
251-
MemberName("io.ktor.http", "contentType"),
252-
ClassName("io.ktor.http", "ContentType", "MultiPart"),
253-
"FormData"
254-
)
248+
is Route.Body.Multipart.Value ->
249+
addStatement("%M(%T.%L)", contentType, ContentType.nested("MultiPart"), "FormData")
255250
}
256251
}
257-
?: this
252+
}
258253

259-
context(OpenAPIContext)
260-
fun CodeBlock.Builder.addBody(bodies: Route.Bodies): CodeBlock.Builder =
261-
bodies.firstNotNullOfOrNull { (_, body) ->
254+
context(OpenAPIContext, CodeBlock.Builder)
255+
fun Route.addBody() {
256+
body.firstNotNullOfOrNull { (_, body) ->
262257
when (body) {
263-
is Route.Body.Json -> addStatement(
264-
"%M(%L)",
265-
MemberName("io.ktor.client.request", "setBody", isExtension = true),
266-
"body"
267-
)
258+
is Route.Body.Json -> addStatement("%M(%L)", setBody, "body")
268259

269260
is Route.Body.Xml -> TODO("Xml input body not supported yet.")
270261
is Route.Body.OctetStream -> TODO("OctetStream input body not supported yet.")
271-
is Route.Body.Multipart.Ref,
272-
is Route.Body.Multipart.Value -> {
273-
body as Route.Body.Multipart
274-
addStatement("%M(", MemberName("io.ktor.client.request", "setBody", isExtension = true))
262+
is Route.Body.Multipart -> {
263+
addStatement("%M(", setBody)
275264
withIndent {
276-
addStatement(
277-
"%M {",
278-
MemberName("io.ktor.client.request.forms", "formData", isExtension = true)
279-
)
265+
addStatement("%M {", formData)
280266
withIndent {
281267
when (body) {
282268
is Route.Body.Multipart.Value ->
@@ -304,7 +290,7 @@ fun CodeBlock.Builder.addBody(bodies: Route.Bodies): CodeBlock.Builder =
304290
}
305291
}
306292
}
307-
?: this
293+
}
308294

309295
context(OpenAPIContext)
310296
fun Route.params(defaults: Boolean): List<ParameterSpec> =
@@ -335,6 +321,7 @@ fun Route.requestBody(defaults: Boolean): List<ParameterSpec> {
335321
description: String?
336322
): ParameterSpec =
337323
ParameterSpec.builder(name, type.toTypeName().copy(nullable = nullable))
324+
.description(description)
338325
.apply { if (defaults && nullable) defaultValue("null") }
339326
.build()
340327

generation/src/main/kotlin/io/github/nomisrev/openapi/Default.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ private fun defaultValueImpl(model: Model): Pair<String, List<Any>>? =
2222
model.cases
2323
.find { it.model.value is Model.Primitive.String }
2424
?.let { case ->
25-
model.default?.let {
25+
model.default?.let { default ->
2626
val typeName = toCaseClassName(model, case.model.value)
27-
Pair("%T(%S)", listOf(typeName, model.default))
27+
Pair("%T(%S)", listOf(typeName, default))
2828
}
2929
}
3030
is Model.Object ->

0 commit comments

Comments
 (0)