插件-vscode AI编程(2)cline-sw
给 cline 加了一个自己的选项。
但是好像改不了参数。暂时不知道怎么和计费关联,猜测这里的统计是一个虚假的数字,真正还是要通过API官网。
实验记录
webview-ui/src/components/setting/ApiOptions.tsx
中添加页面入口选项1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52import {
swaiDefaultModelId,
swaiModels
}
return (
<div style={{ display: "flex", flexDirection: "column", gap: 5, marginBottom: isPopup ? -10 : 0 }}>
<DropdownContainer className="dropdown-container">
<label htmlFor="api-provider">
<span style={{ fontWeight: 500 }}>API Provider</span>
</label>
<VSCodeDropdown
id="api-provider"
value={selectedProvider}
onChange={handleInputChange("apiProvider")}
style={{
minWidth: 130,
position: "relative",
}}>
<VSCodeOption value="swai">SWAI</VSCodeOption>
</VSCodeDropdown>
</DropdownContainer>
{selectedProvider === "swai" && (
<div>
<VSCodeTextField
value={apiConfiguration?.swaiApiKey || ""}
style={{ width: "100%" }}
type="password"
onInput={handleInputChange("swaiApiKey")}
placeholder="Enter API Key...">
<span style={{ fontWeight: 500 }}>SWAI API Key</span>
</VSCodeTextField>
<p
style={{
fontSize: "12px",
marginTop: 3,
color: "var(--vscode-descriptionForeground)",
}}>
This key is stored locally and only used to make API requests from this extension.
{!apiConfiguration?.deepSeekApiKey && (
<VSCodeLink
href="http://ai.thuwaytec.com/"
style={{
display: "inline",
fontSize: "inherit",
}}>
You can get a SWAI API key by signing up here.
</VSCodeLink>
)}
</p>
</div>
)}
}
webview-ui/src/components/chat/ChatView.tsx
中修改聊天界面。
- 业务逻辑
src/core/webview/ClineProvider.ts
中加入 swaiApiKeysrc/shared/api.ts
中加入 API,这里可以配置多个参数。1
2
3
4
5
6
7
8
9
10
11
12
13
14export type SWAIModelId = keyof typeof swaiModels
export const swaiDefaultModelId: SWAIModelId = "deepseek-ai/DeepSeek-R1"
export const swaiModels = {
"deepseek-ai/DeepSeek-R1": {
maxTokens: 8_192,
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.002,
outputPrice: 0.006,
cacheWritesPrice: 0.002,
cacheReadsPrice: 0.006,
},
} as const satisfies Record<string, ModelInfo>src/api/providers/swai.ts
中为重要处理逻辑,这里是根据openAI compatible改的(URL为***/chat/completion,参数一致都能访问)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { withRetry } from "../retry"
import { ApiHandlerOptions, SWAIModelId, ModelInfo, swaiDefaultModelId, swaiModels } from "../../shared/api"
import { ApiHandler } from "../index"
import { calculateApiCostOpenAI } from "../../utils/cost"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
import { convertToR1Format } from "../transform/r1-format"
import { ChatCompletionReasoningEffort } from "openai/resources/chat/completions.mjs"
export class SWAIHandler implements ApiHandler {
private options: ApiHandlerOptions
private client: OpenAI
constructor(options: ApiHandlerOptions) {
this.options = options
this.client = new OpenAI({
baseURL: "http://***/r1/v1/",
// baseURL: "http://api.thuwaytec.com/v1/",
apiKey: this.options.swaiApiKey,
})
}
@withRetry()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const model = this.getModel()
const isDeepseekReasoner = model.id.includes("deepseek-ai/DeepSeek-R1")
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
]
let temperature: number | undefined = 0
let reasoningEffort: ChatCompletionReasoningEffort | undefined = undefined
if (isDeepseekReasoner) {
openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
}
const stream = await this.client.chat.completions.create({
model: model.id,
// max_completion_tokens: model.info.maxTokens,
messages: openAiMessages,
temperature: 1,
reasoning_effort: "medium",
stream: true,
stream_options: { include_usage: true },
// Only set temperature for non-reasoner models
// ...(model.id === "deepseek-ai/DeepSeek-R1" ? {} : { temperature: 0 }),
})
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (delta?.content) {
yield {
type: "text",
text: delta.content,
}
}
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
yield {
type: "reasoning",
reasoning: (delta.reasoning_content as string | undefined) || "",
}
}
if (chunk.usage) {
yield {
type: "usage",
inputTokens: chunk.usage.prompt_tokens || 0,
outputTokens: chunk.usage.completion_tokens || 0,
}
}
}
}
getModel(): { id: SWAIModelId; info: ModelInfo } {
const modelId = this.options.apiModelId
if (modelId && modelId in swaiModels) {
const id = modelId as SWAIModelId
return { id, info: swaiModels[id] }
}
return {
id: swaiDefaultModelId,
info: swaiModels[swaiDefaultModelId],
}
}
}
src/api/index.ts
中引入上面的逻辑处理
1 |
|
附录
官方插件指南
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!