Skip to content

Commit 62dcfbe

Browse files
pugazhendhi-mvigneshsubbiah16
authored andcommitted
Adds unbound provider to roo cline
1 parent db0ec64 commit 62dcfbe

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
lines changed

src/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { DeepSeekHandler } from "./providers/deepseek"
1414
import { MistralHandler } from "./providers/mistral"
1515
import { VsCodeLmHandler } from "./providers/vscode-lm"
1616
import { ApiStream } from "./transform/stream"
17+
import { UnboundHandler } from "./providers/unbound"
1718

1819
export interface SingleCompletionHandler {
1920
completePrompt(prompt: string): Promise<string>
@@ -53,6 +54,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
5354
return new VsCodeLmHandler(options)
5455
case "mistral":
5556
return new MistralHandler(options)
57+
case "unbound":
58+
return new UnboundHandler(options)
5659
default:
5760
return new AnthropicHandler(options)
5861
}

src/api/providers/unbound.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { ApiHandlerOptions, unboundModels, UnboundModelId, unboundDefaultModelId, ModelInfo } from "../../shared/api"
2+
import { ApiStream } from "../transform/stream"
3+
import { Anthropic } from "@anthropic-ai/sdk"
4+
import { ApiHandler } from "../index"
5+
6+
export class UnboundHandler implements ApiHandler {
7+
private unboundApiKey: string
8+
private unboundModelId: string
9+
private unboundBaseUrl: string = "https://ai-gateway-43843357113.us-west1.run.app/v1"
10+
private options: ApiHandlerOptions
11+
12+
constructor(options: ApiHandlerOptions) {
13+
this.options = options
14+
this.unboundApiKey = options.unboundApiKey || ""
15+
this.unboundModelId = options.unboundModelId || ""
16+
}
17+
18+
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
19+
const response = await fetch(`${this.unboundBaseUrl}/chat/completions`, {
20+
method: "POST",
21+
headers: {
22+
Authorization: `Bearer ${this.unboundApiKey}`,
23+
"Content-Type": "application/json",
24+
},
25+
body: JSON.stringify({
26+
model: this.unboundModelId,
27+
messages: [{ role: "system", content: systemPrompt }, ...messages],
28+
}),
29+
})
30+
31+
if (!response.ok) {
32+
throw new Error(`HTTP error! status: ${response.status}`)
33+
}
34+
35+
const data = await response.json()
36+
37+
yield {
38+
type: "text",
39+
text: data.choices[0]?.message?.content || "",
40+
}
41+
yield {
42+
type: "usage",
43+
inputTokens: data.usage?.prompt_tokens || 0,
44+
outputTokens: data.usage?.completion_tokens || 0,
45+
}
46+
}
47+
48+
getModel(): { id: UnboundModelId; info: ModelInfo } {
49+
const modelId = this.options.apiModelId
50+
if (modelId && modelId in unboundModels) {
51+
const id = modelId as UnboundModelId
52+
return { id, info: unboundModels[id] }
53+
}
54+
return {
55+
id: unboundDefaultModelId,
56+
info: unboundModels[unboundDefaultModelId],
57+
}
58+
}
59+
}

src/core/webview/ClineProvider.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ type SecretKey =
6262
| "openAiNativeApiKey"
6363
| "deepSeekApiKey"
6464
| "mistralApiKey"
65+
| "unboundApiKey"
6566
type GlobalStateKey =
6667
| "apiProvider"
6768
| "apiModelId"
@@ -120,6 +121,7 @@ type GlobalStateKey =
120121
| "experimentalDiffStrategy"
121122
| "autoApprovalEnabled"
122123
| "customModes" // Array of custom modes
124+
| "unboundModelId"
123125

124126
export const GlobalFileNames = {
125127
apiConversationHistory: "api_conversation_history.json",
@@ -1309,6 +1311,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
13091311
openRouterUseMiddleOutTransform,
13101312
vsCodeLmModelSelector,
13111313
mistralApiKey,
1314+
unboundApiKey,
1315+
unboundModelId,
13121316
} = apiConfiguration
13131317
await this.updateGlobalState("apiProvider", apiProvider)
13141318
await this.updateGlobalState("apiModelId", apiModelId)
@@ -1347,6 +1351,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
13471351
await this.updateGlobalState("openRouterUseMiddleOutTransform", openRouterUseMiddleOutTransform)
13481352
await this.updateGlobalState("vsCodeLmModelSelector", vsCodeLmModelSelector)
13491353
await this.storeSecret("mistralApiKey", mistralApiKey)
1354+
await this.storeSecret("unboundApiKey", unboundApiKey)
1355+
await this.updateGlobalState("unboundModelId", unboundModelId)
13501356
if (this.cline) {
13511357
this.cline.api = buildApiHandler(apiConfiguration)
13521358
}
@@ -2001,6 +2007,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
20012007
experimentalDiffStrategy,
20022008
autoApprovalEnabled,
20032009
customModes,
2010+
unboundApiKey,
2011+
unboundModelId,
20042012
] = await Promise.all([
20052013
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
20062014
this.getGlobalState("apiModelId") as Promise<string | undefined>,
@@ -2070,6 +2078,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
20702078
this.getGlobalState("experimentalDiffStrategy") as Promise<boolean | undefined>,
20712079
this.getGlobalState("autoApprovalEnabled") as Promise<boolean | undefined>,
20722080
this.customModesManager.getCustomModes(),
2081+
this.getSecret("unboundApiKey") as Promise<string | undefined>,
2082+
this.getGlobalState("unboundModelId") as Promise<string | undefined>,
20732083
])
20742084

20752085
let apiProvider: ApiProvider
@@ -2125,6 +2135,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
21252135
openRouterBaseUrl,
21262136
openRouterUseMiddleOutTransform,
21272137
vsCodeLmModelSelector,
2138+
unboundApiKey,
2139+
unboundModelId,
21282140
},
21292141
lastShownAnnouncementId,
21302142
customInstructions,
@@ -2273,6 +2285,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
22732285
"openAiNativeApiKey",
22742286
"deepSeekApiKey",
22752287
"mistralApiKey",
2288+
"unboundApiKey",
22762289
]
22772290
for (const key of secretKeys) {
22782291
await this.storeSecret(key, undefined)

src/shared/api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type ApiProvider =
1414
| "deepseek"
1515
| "vscode-lm"
1616
| "mistral"
17+
| "unbound"
1718

1819
export interface ApiHandlerOptions {
1920
apiModelId?: string
@@ -57,6 +58,8 @@ export interface ApiHandlerOptions {
5758
deepSeekBaseUrl?: string
5859
deepSeekApiKey?: string
5960
includeMaxTokens?: boolean
61+
unboundApiKey?: string
62+
unboundModelId?: string
6063
}
6164

6265
export type ApiConfiguration = ApiHandlerOptions & {
@@ -593,3 +596,11 @@ export const mistralModels = {
593596
outputPrice: 0.9,
594597
},
595598
} as const satisfies Record<string, ModelInfo>
599+
600+
// Unbound Security
601+
export type UnboundModelId = keyof typeof unboundModels
602+
export const unboundDefaultModelId = "gpt-4o"
603+
export const unboundModels = {
604+
"gpt-4o": openAiNativeModels["gpt-4o"],
605+
"claude-3-5-sonnet-20241022": anthropicModels["claude-3-5-sonnet-20241022"],
606+
} as const satisfies Record<string, ModelInfo>

webview-ui/src/components/settings/ApiOptions.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
openRouterDefaultModelInfo,
2727
vertexDefaultModelId,
2828
vertexModels,
29+
unboundDefaultModelId,
30+
unboundModels,
2931
} from "../../../../src/shared/api"
3032
import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
3133
import { useExtensionState } from "../../context/ExtensionStateContext"
@@ -147,6 +149,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
147149
{ value: "mistral", label: "Mistral" },
148150
{ value: "lmstudio", label: "LM Studio" },
149151
{ value: "ollama", label: "Ollama" },
152+
{ value: "unbound", label: "Unbound" },
150153
]}
151154
/>
152155
</div>
@@ -1283,6 +1286,27 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
12831286
</div>
12841287
)}
12851288

1289+
{selectedProvider === "unbound" && (
1290+
<div>
1291+
<VSCodeTextField
1292+
value={apiConfiguration?.unboundApiKey || ""}
1293+
style={{ width: "100%" }}
1294+
type="password"
1295+
onChange={handleInputChange("unboundApiKey")}
1296+
placeholder="Enter API Key...">
1297+
<span style={{ fontWeight: 500 }}>Unbound API Key</span>
1298+
</VSCodeTextField>
1299+
<p
1300+
style={{
1301+
fontSize: "12px",
1302+
marginTop: 3,
1303+
color: "var(--vscode-descriptionForeground)",
1304+
}}>
1305+
This key is stored locally and only used to make API requests from this extension.
1306+
</p>
1307+
</div>
1308+
)}
1309+
12861310
{apiErrorMessage && (
12871311
<p
12881312
style={{
@@ -1315,6 +1339,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
13151339
{selectedProvider === "openai-native" && createDropdown(openAiNativeModels)}
13161340
{selectedProvider === "deepseek" && createDropdown(deepSeekModels)}
13171341
{selectedProvider === "mistral" && createDropdown(mistralModels)}
1342+
{selectedProvider === "unbound" && createDropdown(unboundModels)}
13181343
</div>
13191344

13201345
<ModelInfoView
@@ -1552,6 +1577,13 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
15521577
supportsImages: false, // VSCode LM API currently doesn't support images
15531578
},
15541579
}
1580+
case "unbound":
1581+
return getProviderData(unboundModels, unboundDefaultModelId)
1582+
// return {
1583+
// selectedProvider: provider,
1584+
// selectedModelId: apiConfiguration?.unboundModelId || unboundDefaultModelId,
1585+
// selectedModelInfo: openAiModelInfoSaneDefaults,
1586+
// }
15551587
default:
15561588
return getProviderData(anthropicModels, anthropicDefaultModelId)
15571589
}

0 commit comments

Comments
 (0)