Skip to content

Commit 1c77225

Browse files
authored
feat(chat): enhance auto model selection display and information handling (#657)
1 parent 04984d8 commit 1c77225

File tree

8 files changed

+73
-63
lines changed

8 files changed

+73
-63
lines changed

src/api/providers/zgsm.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,9 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl
170170
if (fromWorkflow) {
171171
requestOptions.extra_body.prompt_mode = "strict"
172172
}
173-
let stream
174-
let selectedLlm: string | undefined
173+
const isAuto = this.options.zgsmModelId === autoModeModelId
174+
let stream: any
175+
let selectedLLM: string | undefined = this.options.zgsmModelId
175176
let selectReason: string | undefined
176177
try {
177178
this.logger.info(`[RequestID]:`, requestId)
@@ -190,9 +191,8 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl
190191
)
191192
.withResponse()
192193
this.logger.info(`[ResponseID]:`, response.headers.get("x-request-id"))
193-
194-
if (this.options.zgsmModelId === autoModeModelId) {
195-
selectedLlm = response.headers.get("x-select-llm") || ""
194+
if (isAuto) {
195+
selectedLLM = response.headers.get("x-select-llm") || ""
196196
selectReason = response.headers.get("x-select-reason") || ""
197197
const isDev = process.env.NODE_ENV === "development"
198198

@@ -209,7 +209,7 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl
209209
}
210210

211211
// 6. Optimize stream processing - use batch processing and buffer
212-
yield* this.handleOptimizedStream(stream, modelInfo, selectedLlm, selectReason)
212+
yield* this.handleOptimizedStream(stream, modelInfo, isAuto, selectedLLM, selectReason)
213213
} else {
214214
// Non-streaming processing
215215
const requestOptions = this.buildNonStreamingRequestOptions(
@@ -416,7 +416,8 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl
416416
private async *handleOptimizedStream(
417417
stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>,
418418
modelInfo: ModelInfo,
419-
selectedLlm?: string,
419+
isAuto?: boolean,
420+
selectedLLM?: string,
420421
selectReason?: string,
421422
): ApiStream {
422423
const matcher = new XmlMatcher(
@@ -437,10 +438,12 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl
437438
const isDev = process.env.NODE_ENV === "development"
438439

439440
// Yield selected LLM info if available (for Auto model mode)
440-
if (selectedLlm && this.options.zgsmModelId === autoModeModelId) {
441+
if (isAuto) {
441442
yield {
442443
type: "text",
443-
text: `[Selected LLM: ${selectedLlm}${selectReason ? ` (${selectReason})` : ""}]`,
444+
text: `["${selectedLLM}" ${selectReason ? `, "(${selectReason})"` : ""}]`,
445+
isAuto,
446+
originModelId: this.options.zgsmModelId,
444447
}
445448
}
446449

@@ -455,7 +458,7 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl
455458
// Cache content for batch processing
456459
if (delta.content) {
457460
contentBuffer.push(delta.content)
458-
if (isDev && !isPrinted && chunk.model && this.options.zgsmModelId === autoModeModelId) {
461+
if (isDev && !isPrinted && chunk.model && isAuto) {
459462
this.logger.info(`[Current Model]: ${chunk.model}`)
460463
isPrinted = true
461464
}

src/api/transform/stream.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface ApiStreamError {
1717
export interface ApiStreamTextChunk {
1818
type: "text"
1919
text: string
20+
isAuto?: boolean
21+
originModelId?: string
2022
}
2123

2224
export interface ApiStreamReasoningChunk {

src/core/task/Task.ts

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,6 +1918,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
19181918
maxReadFileLine = -1,
19191919
maxReadCharacterLimit = 20000,
19201920
apiRequestBlockHide = true,
1921+
apiConfiguration,
19211922
} = (await this.providerRef.deref()?.getState()) ?? {}
19221923

19231924
await this.say(
@@ -1928,6 +1929,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
19281929
: currentUserContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") +
19291930
"\n\nLoading...",
19301931
apiProtocol,
1932+
originModelId: apiConfiguration?.zgsmModelId,
19311933
}),
19321934
)
19331935

@@ -1970,6 +1972,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
19701972
? undefined
19711973
: finalUserContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"),
19721974
apiProtocol,
1975+
originModelId: apiConfiguration?.zgsmModelId,
19731976
} satisfies ClineApiReqInfo)
19741977

19751978
await this.saveClineMessages()
@@ -2154,32 +2157,25 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
21542157
case "text": {
21552158
// Check if it is Selected LLM information (only in Auto model mode).
21562159
if (
2157-
this.apiConfiguration.zgsmModelId === "Auto" &&
2158-
chunk.text?.startsWith("[Selected LLM:")
2160+
chunk.isAuto &&
2161+
lastApiReqIndex >= 0 &&
2162+
this.clineMessages[lastApiReqIndex] &&
2163+
this.apiConfiguration.apiProvider === "zgsm"
21592164
) {
21602165
// Extract Selected LLM and Reason information and update the api_req_started message.
2161-
const match = chunk.text.match(/\[Selected LLM:\s*([^\]]+)\]/)
2162-
if (match && lastApiReqIndex >= 0 && this.clineMessages[lastApiReqIndex]) {
2163-
const existingData = JSON.parse(
2164-
this.clineMessages[lastApiReqIndex].text || "{}",
2165-
)
2166-
// Parse the model name and reason
2167-
const fullInfo = match[1]
2168-
const reasonMatch = fullInfo.match(/^(.+?)\s*\((.+?)\)$/)
2169-
const selectedLlm = reasonMatch ? reasonMatch[1].trim() : fullInfo.trim()
2170-
const selectReason = reasonMatch ? reasonMatch[2].trim() : undefined
2171-
2172-
this.clineMessages[lastApiReqIndex].text = JSON.stringify({
2173-
...existingData,
2174-
selectedLlm,
2175-
selectReason,
2176-
} satisfies ClineApiReqInfo)
2177-
// Save the selection information but do not add it to the assistant message to avoid it being processed by the parser.
2178-
console.log(
2179-
`[Auto Model] Selected: ${selectedLlm}${selectReason ? ` (${selectReason})` : ""}`,
2180-
)
2181-
break
2182-
}
2166+
const existingData = JSON.parse(this.clineMessages[lastApiReqIndex].text || "{}")
2167+
const [selectedLLM, selectReason] = JSON.parse(chunk.text)
2168+
2169+
this.clineMessages[lastApiReqIndex].text = JSON.stringify({
2170+
...existingData,
2171+
selectedLLM,
2172+
selectReason,
2173+
isAuto: chunk.isAuto,
2174+
originModelId: chunk.originModelId,
2175+
} satisfies ClineApiReqInfo)
2176+
// Save the selection information but do not add it to the assistant message to avoid it being processed by the parser.
2177+
console.log(`[Backend Model Route Detail] ${selectedLLM}${selectReason}`)
2178+
break
21832179
}
21842180

21852181
assistantMessage += chunk.text

src/shared/ExtensionMessage.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,10 @@ export interface ClineApiReqInfo {
496496
cancelReason?: ClineApiReqCancelReason
497497
streamingFailedMessage?: string
498498
apiProtocol?: "anthropic" | "openai"
499-
selectedLlm?: string
499+
selectedLLM?: string
500500
selectReason?: string
501+
isAuto?: boolean
502+
originModelId?: string
501503
}
502504

503505
export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled"

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -265,14 +265,23 @@ export const ChatRowContent = ({
265265
vscode.postMessage({ type: "selectImages", context: "edit", messageTs: message.ts })
266266
}, [message.ts])
267267

268-
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage, selectedLlm, selectReason] = useMemo(() => {
269-
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
270-
const info = safeJsonParse<ClineApiReqInfo>(message.text)
271-
return [info?.cost, info?.cancelReason, info?.streamingFailedMessage, info?.selectedLlm, info?.selectReason]
272-
}
268+
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage, selectedLLM, selectReason, isAuto, originModelId] =
269+
useMemo(() => {
270+
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
271+
const info = safeJsonParse<ClineApiReqInfo>(message.text)
272+
return [
273+
info?.cost,
274+
info?.cancelReason,
275+
info?.streamingFailedMessage,
276+
info?.selectedLLM,
277+
info?.selectReason,
278+
info?.isAuto,
279+
info?.originModelId,
280+
]
281+
}
273282

274-
return [undefined, undefined, undefined, undefined, undefined]
275-
}, [message.text, message.say])
283+
return [undefined, undefined, undefined, undefined, undefined]
284+
}, [message.text, message.say])
276285

277286
// When resuming task, last wont be api_req_failed but a resume_task
278287
// message, so api_req_started will show loading spinner. That's why we just
@@ -1196,24 +1205,22 @@ export const ChatRowContent = ({
11961205
${Number(cost || 0)?.toFixed(4)}
11971206
</div>
11981207
</div>
1199-
{(selectedLlm || selectReason) && (
1200-
<div className="mt-2 flex items-center flex-wrap gap-2">
1201-
{selectedLlm && (
1202-
<div
1203-
className="text-xs text-vscode-descriptionForeground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
1204-
title="Selected Model">
1205-
{t("chat:autoMode.selectedLlm", { selectedLlm })}
1206-
</div>
1207-
)}
1208-
{selectReason && (
1209-
<div
1210-
className="text-xs text-vscode-descriptionForeground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
1211-
title="Selection Reason">
1212-
{t("chat:autoMode.selectReason", { selectReason })}
1213-
</div>
1214-
)}
1215-
</div>
1216-
)}
1208+
<div className="mt-2 flex items-center flex-wrap gap-2">
1209+
{(selectedLLM || originModelId) && (
1210+
<div
1211+
className="text-xs text-vscode-descriptionForeground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
1212+
title="Selected Model">
1213+
{isAuto ? t("chat:autoMode.selectedLLM", { selectedLLM }) : originModelId}
1214+
</div>
1215+
)}
1216+
{selectReason && (
1217+
<div
1218+
className="text-xs text-vscode-descriptionForeground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
1219+
title="Selection Reason">
1220+
{t("chat:autoMode.selectReason", { selectReason })}
1221+
</div>
1222+
)}
1223+
</div>
12171224
{(((cost === null || cost === undefined) && apiRequestFailedMessage) ||
12181225
apiReqStreamingFailedMessage) && (
12191226
<ErrorRow

webview-ui/src/i18n/costrict-i18n/locales/en/chat.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127
}
128128
},
129129
"autoMode": {
130-
"selectedLlm": "Auto model: {{selectedLlm}}",
130+
"selectedLLM": "Auto model: {{selectedLLM}}",
131131
"selectReason": "Select reason: {{selectReason}}"
132132
}
133133
}

webview-ui/src/i18n/costrict-i18n/locales/zh-CN/chat.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/costrict-i18n/locales/zh-TW/chat.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)