Skip to content

Commit cf9826d

Browse files
committed
feat(llm): filter disabled github copilot models
1 parent 12cde8b commit cf9826d

File tree

5 files changed

+223
-10
lines changed

5 files changed

+223
-10
lines changed

core/src/main/kotlin/cc/unitmesh/devti/llm2/GithubCopilotDetector.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,21 @@ object GithubCopilotDetector {
114114
// Parse the JSON response
115115
val json = Json { ignoreUnknownKeys = true }
116116
println(responseBody)
117-
val models = json.decodeFromString<CopilotModelsResponse>(responseBody)
117+
val parsedResponse = json.decodeFromString<CopilotModelsResponse>(responseBody)
118+
119+
// Filter models to only include those with enabled policy state
120+
// Keep models where policy is null (backward compatibility) or policy.state is "enabled"
121+
val filteredModels = parsedResponse.data.filter { model ->
122+
model.policy?.state == "enabled" || model.policy == null
123+
}
124+
125+
val originalCount = parsedResponse.data.size
126+
val filteredCount = filteredModels.size
127+
if (originalCount != filteredCount) {
128+
logger.info("Filtered GitHub Copilot models: $originalCount -> $filteredCount (removed ${originalCount - filteredCount} disabled models)")
129+
}
130+
131+
val models = CopilotModelsResponse(data = filteredModels)
118132

119133
// Update cache
120134
cachedModels = models

core/src/main/kotlin/cc/unitmesh/devti/llm2/GithubCopilotManager.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,12 @@ class GithubCopilotManager() : Disposable {
8484
}
8585

8686
logger.info("Initializing GitHub Copilot models...")
87-
val models = withContext(Dispatchers.IO) {
88-
GithubCopilotDetector.getSupportedModels(forceRefresh = true)?.data
87+
val modelsResponse = withContext(Dispatchers.IO) {
88+
GithubCopilotDetector.getSupportedModels(forceRefresh = true)
8989
}
9090

91-
if (models != null) {
91+
if (modelsResponse != null) {
92+
val models = modelsResponse.data
9293
modelsCache = models
9394
lastUpdateTime = System.currentTimeMillis()
9495
isInitialized = true
@@ -118,16 +119,17 @@ class GithubCopilotManager() : Disposable {
118119
fun getSupportedModels(forceRefresh: Boolean = false): List<CopilotModel>? {
119120
if (forceRefresh) {
120121
// 如果强制刷新,则直接请求新数据
121-
val freshModels = GithubCopilotDetector.getSupportedModels(forceRefresh = true)
122-
if (freshModels != null) {
123-
modelsCache = freshModels.data
122+
val freshModelsResponse = GithubCopilotDetector.getSupportedModels(forceRefresh = true)
123+
if (freshModelsResponse != null) {
124+
val freshModels = freshModelsResponse.data
125+
modelsCache = freshModels
124126
lastUpdateTime = System.currentTimeMillis()
125127
isInitialized = true
126128

127129
// 通知所有监听器
128130
notifyListeners(modelsCache)
129131
}
130-
return freshModels?.data ?: emptyList()
132+
return freshModelsResponse?.data ?: emptyList()
131133
}
132134

133135
// 如果已初始化并且缓存存在,直接返回缓存

core/src/main/kotlin/cc/unitmesh/devti/llm2/GithubCopilotProvider.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,23 @@ private object GithubOAuthProvider {
8181
val responseBody = response.body?.string() ?: throw IllegalStateException("响应体为空")
8282
// 使用kotlinx.serialization解析JSON响应
8383
val json = Json { ignoreUnknownKeys = true }
84-
json.decodeFromString<CopilotModelsResponse>(responseBody).also {
85-
supportedModels = it
84+
val parsedResponse = json.decodeFromString<CopilotModelsResponse>(responseBody)
85+
86+
// Filter models to only include those with enabled policy state
87+
// Keep models where policy is null (backward compatibility) or policy.state is "enabled"
88+
val filteredModels = parsedResponse.data.filter { model ->
89+
model.policy?.state == "enabled" || model.policy == null
90+
}
91+
92+
val originalCount = parsedResponse.data.size
93+
val filteredCount = filteredModels.size
94+
if (originalCount != filteredCount) {
95+
logger.info("Filtered GitHub Copilot models: $originalCount -> $filteredCount (removed ${originalCount - filteredCount} disabled models)")
8696
}
97+
98+
val filteredResponse = CopilotModelsResponse(data = filteredModels)
99+
supportedModels = filteredResponse
100+
filteredResponse
87101
} else {
88102
logger.warn("获取支持的模型列表失败: ${response.code}")
89103
null

core/src/main/kotlin/cc/unitmesh/devti/llm2/LLMProvider2.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ abstract class LLMProvider2 protected constructor(
226226
// Use the provided model name or get the selected model ID from the settings state
227227
val settings = AutoDevSettingsState.getInstance()
228228
val actualModelName = modelName ?: settings.selectedCompletionModelId.takeIf { it.isNotEmpty() } ?: "gpt-4"
229+
println("Using model: $actualModelName")
229230

230231
return GithubCopilotProvider(
231232
responseResolver = if (stream) "\$.choices[0].delta.content" else "\$.choices[0].message.content",
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package cc.unitmesh.devti.llm2.model
2+
3+
import kotlinx.serialization.json.Json
4+
import org.junit.Test
5+
import kotlin.test.assertEquals
6+
7+
class CopilotModelFilterTest {
8+
9+
@Test
10+
fun `should filter out models with disabled policy state`() {
11+
// Given
12+
val json = Json { ignoreUnknownKeys = true }
13+
val responseJson = """
14+
{
15+
"data": [
16+
{
17+
"id": "gpt-4o",
18+
"name": "GPT-4o",
19+
"object": "model",
20+
"vendor": "openai",
21+
"version": "2024-05-13",
22+
"preview": false,
23+
"model_picker_enabled": true,
24+
"capabilities": {
25+
"type": "chat"
26+
},
27+
"policy": {
28+
"state": "enabled"
29+
}
30+
},
31+
{
32+
"id": "claude-3-5-sonnet",
33+
"name": "Claude 3.5 Sonnet",
34+
"object": "model",
35+
"vendor": "anthropic",
36+
"version": "20241022",
37+
"preview": false,
38+
"model_picker_enabled": true,
39+
"capabilities": {
40+
"type": "chat"
41+
},
42+
"policy": {
43+
"state": "disabled"
44+
}
45+
},
46+
{
47+
"id": "o1-preview",
48+
"name": "o1-preview",
49+
"object": "model",
50+
"vendor": "openai",
51+
"version": "2024-09-12",
52+
"preview": true,
53+
"model_picker_enabled": true,
54+
"capabilities": {
55+
"type": "chat"
56+
}
57+
}
58+
]
59+
}
60+
""".trimIndent()
61+
62+
// When
63+
val parsedResponse = json.decodeFromString<CopilotModelsResponse>(responseJson)
64+
65+
// Apply the same filtering logic as in the actual code
66+
val filteredModels = parsedResponse.data.filter { model ->
67+
model.policy?.state == "enabled" || model.policy == null
68+
}
69+
70+
// Then
71+
assertEquals(3, parsedResponse.data.size, "Original response should have 3 models")
72+
assertEquals(2, filteredModels.size, "Filtered response should have 2 models")
73+
74+
// Verify the correct models are kept
75+
val filteredIds = filteredModels.map { it.id }
76+
assertEquals(listOf("gpt-4o", "o1-preview"), filteredIds)
77+
78+
// Verify the disabled model is filtered out
79+
val disabledModel = parsedResponse.data.find { it.id == "claude-3-5-sonnet" }
80+
assertEquals("disabled", disabledModel?.policy?.state)
81+
assert(!filteredIds.contains("claude-3-5-sonnet"))
82+
}
83+
84+
@Test
85+
fun `should keep all models when all have enabled policy state`() {
86+
// Given
87+
val json = Json { ignoreUnknownKeys = true }
88+
val responseJson = """
89+
{
90+
"data": [
91+
{
92+
"id": "gpt-4o",
93+
"name": "GPT-4o",
94+
"object": "model",
95+
"vendor": "openai",
96+
"version": "2024-05-13",
97+
"preview": false,
98+
"model_picker_enabled": true,
99+
"capabilities": {
100+
"type": "chat"
101+
},
102+
"policy": {
103+
"state": "enabled"
104+
}
105+
},
106+
{
107+
"id": "claude-3-5-sonnet",
108+
"name": "Claude 3.5 Sonnet",
109+
"object": "model",
110+
"vendor": "anthropic",
111+
"version": "20241022",
112+
"preview": false,
113+
"model_picker_enabled": true,
114+
"capabilities": {
115+
"type": "chat"
116+
},
117+
"policy": {
118+
"state": "enabled"
119+
}
120+
}
121+
]
122+
}
123+
""".trimIndent()
124+
125+
// When
126+
val parsedResponse = json.decodeFromString<CopilotModelsResponse>(responseJson)
127+
128+
// Apply the same filtering logic as in the actual code
129+
val filteredModels = parsedResponse.data.filter { model ->
130+
model.policy?.state == "enabled" || model.policy == null
131+
}
132+
133+
// Then
134+
assertEquals(2, parsedResponse.data.size, "Original response should have 2 models")
135+
assertEquals(2, filteredModels.size, "Filtered response should have 2 models")
136+
137+
// Verify all models are kept
138+
val filteredIds = filteredModels.map { it.id }
139+
assertEquals(listOf("gpt-4o", "claude-3-5-sonnet"), filteredIds)
140+
}
141+
142+
@Test
143+
fun `should keep models with null policy for backward compatibility`() {
144+
// Given
145+
val json = Json { ignoreUnknownKeys = true }
146+
val responseJson = """
147+
{
148+
"data": [
149+
{
150+
"id": "legacy-model",
151+
"name": "Legacy Model",
152+
"object": "model",
153+
"vendor": "openai",
154+
"version": "2024-01-01",
155+
"preview": false,
156+
"model_picker_enabled": true,
157+
"capabilities": {
158+
"type": "chat"
159+
}
160+
}
161+
]
162+
}
163+
""".trimIndent()
164+
165+
// When
166+
val parsedResponse = json.decodeFromString<CopilotModelsResponse>(responseJson)
167+
168+
// Apply the same filtering logic as in the actual code
169+
val filteredModels = parsedResponse.data.filter { model ->
170+
model.policy?.state == "enabled" || model.policy == null
171+
}
172+
173+
// Then
174+
assertEquals(1, parsedResponse.data.size, "Original response should have 1 model")
175+
assertEquals(1, filteredModels.size, "Filtered response should have 1 model")
176+
177+
// Verify the model with null policy is kept
178+
val filteredModel = filteredModels.first()
179+
assertEquals("legacy-model", filteredModel.id)
180+
assertEquals(null, filteredModel.policy)
181+
}
182+
}

0 commit comments

Comments
 (0)