Skip to content

Commit 3314f19

Browse files
committed
libraries: Add OpenAI Compatible backend
Signed-off-by: AkaneTan <69606737+AkaneTan@users.noreply.github.com>
1 parent c9876ce commit 3314f19

35 files changed

Lines changed: 4944 additions & 0 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
plugins {
22
id("alexandrite.kotlin-library")
33
}
4+
5+
dependencies {
6+
api(libs.kotlinx.coroutines.core)
7+
8+
testImplementation(libs.kotlinx.coroutines.test)
9+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package org.foedusprogramme.alexandrite.internal.http
2+
3+
import java.io.IOException
4+
import java.net.http.HttpHeaders
5+
import java.time.Instant
6+
import java.time.ZonedDateTime
7+
import java.time.format.DateTimeFormatter
8+
import java.time.format.DateTimeParseException
9+
import kotlin.time.Duration
10+
import kotlin.time.Duration.Companion.milliseconds
11+
import kotlin.time.Duration.Companion.seconds
12+
import kotlin.time.toKotlinDuration
13+
14+
/** An HTTP call that failed. */
15+
public class HttpException(
16+
public val kind: HttpFailureKind,
17+
message: String,
18+
/** The response's status, null when no response came. */
19+
public val status: Int? = null,
20+
/** How long the server asked to wait, null when it did not say. */
21+
public val retryAfter: Duration? = null,
22+
/** The server's id of the request. */
23+
public val requestId: String? = null,
24+
/** The start of the response body, its secrets masked, null when there is none. */
25+
public val body: String? = null,
26+
/** Whether the response's body had started when the call failed. */
27+
public val afterResponse: Boolean = false,
28+
cause: Throwable? = null,
29+
) : IOException(message, cause)
30+
31+
/** What kind of failure an [HttpException] is, and whether the same request may succeed when sent again. */
32+
public enum class HttpFailureKind(public val retryable: Boolean) {
33+
AUTHENTICATION(false),
34+
PERMISSION_DENIED(false),
35+
QUOTA_EXHAUSTED(false),
36+
NOT_FOUND(false),
37+
REQUEST_TOO_LARGE(false),
38+
INVALID_REQUEST(false),
39+
RATE_LIMITED(true),
40+
OVERLOADED(true),
41+
SERVER_ERROR(true),
42+
43+
/** A server too slow for the call's timeouts or a 408 / 504 status. */
44+
TIMEOUT(true),
45+
46+
/** A server that cannot be reached or a connection that breaks. */
47+
CONNECTION(true),
48+
49+
/** A response that breaks HTTP or what the call expects, such as a redirect. */
50+
PROTOCOL(false),
51+
;
52+
53+
public companion object {
54+
/** The kind of a response with [status], which is no 2xx status. */
55+
public fun of(status: Int): HttpFailureKind = when (status) {
56+
401 -> AUTHENTICATION
57+
402 -> QUOTA_EXHAUSTED
58+
403 -> PERMISSION_DENIED
59+
404 -> NOT_FOUND
60+
408, 504 -> TIMEOUT
61+
409 -> SERVER_ERROR
62+
413 -> REQUEST_TOO_LARGE
63+
429 -> RATE_LIMITED
64+
503, 529 -> OVERLOADED
65+
in 400..499 -> INVALID_REQUEST
66+
in 500..599 -> SERVER_ERROR
67+
else -> PROTOCOL
68+
}
69+
}
70+
}
71+
72+
/** The wait that [headers] ask for at [now] in `retry-after-ms` or `retry-after`, null when they ask for none. */
73+
public fun retryAfter(headers: HttpHeaders, now: Instant): Duration? {
74+
headers.firstValue("retry-after-ms").orElse(null)?.trim()?.toDoubleOrNull()
75+
?.takeIf { it.isFinite() && it >= 0 }
76+
?.let { return it.milliseconds }
77+
val value = headers.firstValue("retry-after").orElse(null)?.trim() ?: return null
78+
value.toDoubleOrNull()?.let { return if (it.isFinite() && it >= 0) it.seconds else null }
79+
return try {
80+
val at = ZonedDateTime.parse(value, DateTimeFormatter.RFC_1123_DATE_TIME).toInstant()
81+
java.time.Duration.between(now, at).coerceAtLeast(java.time.Duration.ZERO).toKotlinDuration()
82+
} catch (e: DateTimeParseException) {
83+
null
84+
}
85+
}
86+
87+
/** The server's id of the request in [headers], null when they hold none. */
88+
public fun requestId(headers: HttpHeaders): String? = REQUEST_ID_HEADERS.firstNotNullOfOrNull { name ->
89+
headers.firstValue(name).orElse(null)?.takeIf { it.isNotBlank() }
90+
}
91+
92+
private val REQUEST_ID_HEADERS = listOf("x-request-id", "request-id", "x-amzn-requestid", "x-amz-request-id")

0 commit comments

Comments
 (0)