Skip to content
This repository was archived by the owner on Jan 24, 2026. It is now read-only.

Commit e6afad3

Browse files
committed
feat: add KtorClientConfig for customizable HTTP client settings
1 parent 34e1aec commit e6afad3

4 files changed

Lines changed: 324 additions & 14 deletions

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,19 @@ When first generating the config file, it will be created with schema annotation
3838
## Example config file
3939

4040
```yaml
41-
# $schema: https://github.com/Stream29/ProxyAsLocalModel/raw/master/config_v0.schema.json
41+
# $schema: https://github.com/Stream29/ProxyAsLocalModel/raw/master/config_v1.schema.json
4242
lmStudio:
4343
port: 1234 # This is default value
4444
enabled: true # This is default value
4545
ollama:
4646
port: 11434 # This is default value
4747
enabled: true # This is default value
48+
client:
49+
socketTimeout: 1919810 # Long.MAX_VALUE is default value, in milliseconds
50+
connectionTimeout: 1919810 # Long.MAX_VALUE is default value, in milliseconds
51+
requestTimeout: 1919810 # Long.MAX_VALUE is default value, in milliseconds
52+
retry: 3 # This is default value
53+
delayBeforeRetry: 1000 # This is default value, in milliseconds
4854

4955
apiProviders:
5056
OpenAI:

config_v1.schema.json

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"lmStudio": {
5+
"type": "object",
6+
"properties": {
7+
"port": {
8+
"type": "integer",
9+
"minimum": -2147483648,
10+
"maximum": 2147483647
11+
},
12+
"host": {
13+
"type": "string"
14+
},
15+
"enabled": {
16+
"type": "boolean"
17+
}
18+
}
19+
},
20+
"ollama": {
21+
"type": "object",
22+
"properties": {
23+
"port": {
24+
"type": "integer",
25+
"minimum": -2147483648,
26+
"maximum": 2147483647
27+
},
28+
"host": {
29+
"type": "string"
30+
},
31+
"enabled": {
32+
"type": "boolean"
33+
}
34+
}
35+
},
36+
"client": {
37+
"type": "object",
38+
"properties": {
39+
"socketTimeout": {
40+
"type": "integer"
41+
},
42+
"connectTimeout": {
43+
"type": "integer"
44+
},
45+
"requestTimeout": {
46+
"type": "integer"
47+
},
48+
"retry": {
49+
"type": "integer",
50+
"minimum": -2147483648,
51+
"maximum": 2147483647
52+
},
53+
"delayBeforeRetry": {
54+
"type": "integer"
55+
}
56+
}
57+
},
58+
"apiProviders": {
59+
"type": "object",
60+
"additionalProperties": {
61+
"$ref": "#/$defs/ApiProvider"
62+
}
63+
}
64+
},
65+
"$defs": {
66+
"ApiProvider": {
67+
"anyOf": [
68+
{
69+
"allOf": [
70+
{
71+
"type": "object",
72+
"properties": {
73+
"apiKey": {
74+
"type": "string"
75+
},
76+
"modelList": {
77+
"type": "array",
78+
"items": {
79+
"type": "string"
80+
}
81+
}
82+
},
83+
"required": [
84+
"apiKey"
85+
]
86+
},
87+
{
88+
"properties": {
89+
"type": {
90+
"enum": [
91+
"DashScope"
92+
]
93+
}
94+
},
95+
"required": [
96+
"type"
97+
]
98+
}
99+
]
100+
},
101+
{
102+
"allOf": [
103+
{
104+
"type": "object",
105+
"properties": {
106+
"apiKey": {
107+
"type": "string"
108+
},
109+
"modelList": {
110+
"type": "array",
111+
"items": {
112+
"type": "string"
113+
}
114+
}
115+
},
116+
"required": [
117+
"apiKey"
118+
]
119+
},
120+
{
121+
"properties": {
122+
"type": {
123+
"enum": [
124+
"DeepSeek"
125+
]
126+
}
127+
},
128+
"required": [
129+
"type"
130+
]
131+
}
132+
]
133+
},
134+
{
135+
"allOf": [
136+
{
137+
"type": "object",
138+
"properties": {
139+
"apiKey": {
140+
"type": "string"
141+
},
142+
"modelList": {
143+
"type": "array",
144+
"items": {
145+
"type": "string"
146+
}
147+
}
148+
},
149+
"required": [
150+
"apiKey",
151+
"modelList"
152+
]
153+
},
154+
{
155+
"properties": {
156+
"type": {
157+
"enum": [
158+
"Gemini"
159+
]
160+
}
161+
},
162+
"required": [
163+
"type"
164+
]
165+
}
166+
]
167+
},
168+
{
169+
"allOf": [
170+
{
171+
"type": "object",
172+
"properties": {
173+
"apiKey": {
174+
"type": "string"
175+
},
176+
"modelList": {
177+
"type": "array",
178+
"items": {
179+
"type": "string"
180+
}
181+
}
182+
},
183+
"required": [
184+
"apiKey"
185+
]
186+
},
187+
{
188+
"properties": {
189+
"type": {
190+
"enum": [
191+
"Mistral"
192+
]
193+
}
194+
},
195+
"required": [
196+
"type"
197+
]
198+
}
199+
]
200+
},
201+
{
202+
"allOf": [
203+
{
204+
"type": "object",
205+
"properties": {
206+
"baseUrl": {
207+
"type": "string"
208+
},
209+
"apiKey": {
210+
"type": "string"
211+
},
212+
"modelList": {
213+
"type": "array",
214+
"items": {
215+
"type": "string"
216+
}
217+
}
218+
},
219+
"required": [
220+
"baseUrl",
221+
"apiKey",
222+
"modelList"
223+
]
224+
},
225+
{
226+
"properties": {
227+
"type": {
228+
"enum": [
229+
"OpenAi"
230+
]
231+
}
232+
},
233+
"required": [
234+
"type"
235+
]
236+
}
237+
]
238+
},
239+
{
240+
"allOf": [
241+
{
242+
"type": "object",
243+
"properties": {
244+
"apiKey": {
245+
"type": "string"
246+
},
247+
"modelList": {
248+
"type": "array",
249+
"items": {
250+
"type": "string"
251+
}
252+
}
253+
},
254+
"required": [
255+
"apiKey",
256+
"modelList"
257+
]
258+
},
259+
{
260+
"properties": {
261+
"type": {
262+
"enum": [
263+
"SiliconFlow"
264+
]
265+
}
266+
},
267+
"required": [
268+
"type"
269+
]
270+
}
271+
]
272+
}
273+
]
274+
}
275+
}
276+
}

src/main/kotlin/io/github/stream29/proxy/Config.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable
77
data class Config(
88
val lmStudio: LmStudioConfig = LmStudioConfig(),
99
val ollama: OllamaConfig = OllamaConfig(),
10+
val client: KtorClientConfig = KtorClientConfig(),
1011
val apiProviders: Map<String, ApiProvider> = emptyMap(),
1112
)
1213

@@ -22,4 +23,13 @@ data class OllamaConfig(
2223
val port: Int = 11435,
2324
val host: String = "0.0.0.0",
2425
val enabled: Boolean = true,
26+
)
27+
28+
@Serializable
29+
data class KtorClientConfig(
30+
val socketTimeout: Long = Long.MAX_VALUE,
31+
val connectTimeout: Long = Long.MAX_VALUE,
32+
val requestTimeout: Long = Long.MAX_VALUE,
33+
val retry: Int = 3,
34+
val delayBeforeRetry: Long = 1000,
2535
)

src/main/kotlin/io/github/stream29/proxy/Global.kt

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,6 @@ val globalYaml = Yaml(
5151
)
5252
)
5353

54-
val globalClient = HttpClient(io.ktor.client.engine.cio.CIO) {
55-
install(ContentNegotiation) {
56-
json(globalJson)
57-
}
58-
install(HttpTimeout) {
59-
socketTimeoutMillis = 10000
60-
connectTimeoutMillis = 10000
61-
requestTimeoutMillis = Long.MAX_VALUE
62-
}
63-
expectSuccess = true
64-
}
65-
6654
val configFile = File("config.yml")
6755

6856
@Suppress("unused")
@@ -72,7 +60,7 @@ val unused = {
7260
helpLogger.info("A default config file is created at ${configFile.absolutePath} with schema annotation.")
7361
configFile.writeText(
7462
"""
75-
# ${'$'}schema: https://github.com/Stream29/ProxyAsLocalModel/raw/master/config_v0.schema.json
63+
# ${'$'}schema: https://github.com/Stream29/ProxyAsLocalModel/raw/master/config_v1.schema.json
7664
lmStudio:
7765
port: 1234
7866
ollama:
@@ -96,6 +84,11 @@ apiProviders: {}
9684
previousApiProviders.values.forEach { it.close() }
9785
apiProviderProperty.set(newConfig.apiProviders)
9886
}
87+
if (previousConfig.client != newConfig.client) {
88+
val previousClient = globalClient
89+
previousClient.close()
90+
clientConfigProperty.set(newConfig.client)
91+
}
9992
if (previousConfig.lmStudio != newConfig.lmStudio) {
10093
val previousServer = lmStudioServer
10194
previousServer?.stop()
@@ -121,6 +114,31 @@ private val configProperty = AutoUpdatePropertyRoot(
121114

122115
var config by configProperty
123116

117+
val clientConfigProperty = AutoUpdatePropertyRoot(
118+
sync = true,
119+
mode = AutoUpdateMode.PROPAGATE,
120+
initValue = config.client
121+
)
122+
123+
val globalClient by clientConfigProperty.subproperty {
124+
configLogger.info("Ktor Client created with: $it")
125+
HttpClient(io.ktor.client.engine.cio.CIO) {
126+
install(ContentNegotiation) {
127+
json(globalJson)
128+
}
129+
install(HttpTimeout) {
130+
socketTimeoutMillis = it.socketTimeout
131+
connectTimeoutMillis = it.connectTimeout
132+
requestTimeoutMillis = it.requestTimeout
133+
}
134+
install(HttpRequestRetry) {
135+
retryOnException(maxRetries = it.retry)
136+
constantDelay(it.delayBeforeRetry)
137+
}
138+
expectSuccess = true
139+
}
140+
}
141+
124142
private val apiProviderProperty = AutoUpdatePropertyRoot(
125143
sync = true,
126144
mode = AutoUpdateMode.PROPAGATE,

0 commit comments

Comments
 (0)