mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add asksage support (#2011)
* feat: add asksage support * chore: create changeset for asksage support * Fix Typo Fix Typo * chore: fix lint * Fixes * Validate asksage API key --------- Co-authored-by: Dennis Bartlett <bartlett.dc.1@gmail.com> Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
This commit is contained in:
parent
3d34de4908
commit
d103f8ee62
8 changed files with 216 additions and 0 deletions
5
.changeset/green-forks-change.md
Normal file
5
.changeset/green-forks-change.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add support for AskSage as model provider.
|
||||
|
|
@ -17,6 +17,7 @@ import { QwenHandler } from "./providers/qwen"
|
|||
import { MistralHandler } from "./providers/mistral"
|
||||
import { VsCodeLmHandler } from "./providers/vscode-lm"
|
||||
import { LiteLlmHandler } from "./providers/litellm"
|
||||
import { AskSageHandler } from "./providers/asksage"
|
||||
import { XAIHandler } from "./providers/xai"
|
||||
|
||||
export interface ApiHandler {
|
||||
|
|
@ -63,6 +64,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
|||
return new VsCodeLmHandler(options)
|
||||
case "litellm":
|
||||
return new LiteLlmHandler(options)
|
||||
case "asksage":
|
||||
return new AskSageHandler(options)
|
||||
case "xai":
|
||||
return new XAIHandler(options)
|
||||
default:
|
||||
|
|
|
|||
115
src/api/providers/asksage.ts
Normal file
115
src/api/providers/asksage.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandler } from ".."
|
||||
import {
|
||||
ApiHandlerOptions,
|
||||
ModelInfo,
|
||||
AskSageModelId,
|
||||
askSageModels,
|
||||
askSageDefaultModelId,
|
||||
askSageDefaultURL,
|
||||
} from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
type AskSageRequest = {
|
||||
system_prompt: string
|
||||
message: {
|
||||
user: "gpt" | "me"
|
||||
message: string
|
||||
}[]
|
||||
model: string
|
||||
dataset: "none"
|
||||
}
|
||||
|
||||
type AskSageResponse = {
|
||||
uuid: string
|
||||
status: number
|
||||
// Response status
|
||||
response: string
|
||||
// Generated response message
|
||||
message: string
|
||||
}
|
||||
|
||||
export class AskSageHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private apiUrl: string
|
||||
private apiKey: string
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
console.log("init api url", options.asksageApiUrl, askSageDefaultURL)
|
||||
this.options = options
|
||||
this.apiKey = options.asksageApiKey || ""
|
||||
this.apiUrl = options.asksageApiUrl || askSageDefaultURL
|
||||
|
||||
if (!this.apiKey) {
|
||||
throw new Error("AskSage API key is required")
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
try {
|
||||
const model = this.getModel()
|
||||
|
||||
// Transform messages into AskSageRequest format
|
||||
const formattedMessages = messages.map((msg) => {
|
||||
const content = Array.isArray(msg.content)
|
||||
? msg.content.map((block) => ("text" in block ? block.text : "")).join("")
|
||||
: msg.content
|
||||
|
||||
return {
|
||||
user: msg.role === "assistant" ? ("gpt" as const) : ("me" as const),
|
||||
message: content,
|
||||
}
|
||||
})
|
||||
|
||||
const request: AskSageRequest = {
|
||||
system_prompt: systemPrompt,
|
||||
message: formattedMessages,
|
||||
model: model.id,
|
||||
dataset: "none",
|
||||
}
|
||||
|
||||
// Make request to AskSage API
|
||||
const response = await fetch(`${this.apiUrl}/query`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-access-tokens": this.apiKey,
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
throw new Error(`AskSage API error: ${error}`)
|
||||
}
|
||||
|
||||
const result = (await response.json()) as AskSageResponse
|
||||
|
||||
if (!result.message) {
|
||||
throw new Error("No content in AskSage response")
|
||||
}
|
||||
|
||||
// Return entire response as a single chunk since streaming is not supported
|
||||
yield {
|
||||
type: "text",
|
||||
text: result.message,
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(`AskSage request failed: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in askSageModels) {
|
||||
const id = modelId as AskSageModelId
|
||||
return { id, info: askSageModels[id] }
|
||||
}
|
||||
return {
|
||||
id: askSageDefaultModelId,
|
||||
info: askSageModels[askSageDefaultModelId],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ type SecretKey =
|
|||
| "liteLlmApiKey"
|
||||
| "authToken"
|
||||
| "authNonce"
|
||||
| "asksageApiKey"
|
||||
| "xaiApiKey"
|
||||
type GlobalStateKey =
|
||||
| "apiProvider"
|
||||
|
|
@ -100,7 +101,9 @@ type GlobalStateKey =
|
|||
| "togetherModelId"
|
||||
| "mcpMarketplaceCatalog"
|
||||
| "telemetrySetting"
|
||||
| "asksageApiUrl"
|
||||
| "thinkingBudgetTokens"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
uiMessages: "ui_messages.json",
|
||||
|
|
@ -596,6 +599,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
liteLlmModelId,
|
||||
liteLlmApiKey,
|
||||
qwenApiLine,
|
||||
asksageApiKey,
|
||||
asksageApiUrl,
|
||||
xaiApiKey,
|
||||
thinkingBudgetTokens,
|
||||
} = message.apiConfiguration
|
||||
|
|
@ -640,6 +645,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
await this.updateGlobalState("qwenApiLine", qwenApiLine)
|
||||
await this.updateGlobalState("requestyModelId", requestyModelId)
|
||||
await this.updateGlobalState("togetherModelId", togetherModelId)
|
||||
await this.storeSecret("asksageApiKey", asksageApiKey)
|
||||
await this.updateGlobalState("asksageApiUrl", asksageApiUrl)
|
||||
await this.updateGlobalState("thinkingBudgetTokens", thinkingBudgetTokens)
|
||||
if (this.cline) {
|
||||
this.cline.api = buildApiHandler(message.apiConfiguration)
|
||||
|
|
@ -1007,6 +1014,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "asksage":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.apiModelId)
|
||||
break
|
||||
case "openrouter":
|
||||
|
|
@ -1043,6 +1051,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "asksage":
|
||||
await this.updateGlobalState("apiModelId", newModelId)
|
||||
break
|
||||
case "openrouter":
|
||||
|
|
@ -1904,6 +1913,8 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
|||
qwenApiLine,
|
||||
liteLlmApiKey,
|
||||
telemetrySetting,
|
||||
asksageApiKey,
|
||||
asksageApiUrl,
|
||||
xaiApiKey,
|
||||
thinkingBudgetTokens,
|
||||
] = await Promise.all([
|
||||
|
|
@ -1960,6 +1971,8 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
|||
this.getGlobalState("qwenApiLine") as Promise<string | undefined>,
|
||||
this.getSecret("liteLlmApiKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("telemetrySetting") as Promise<TelemetrySetting | undefined>,
|
||||
this.getSecret("asksageApiKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("asksageApiUrl") as Promise<string | undefined>,
|
||||
this.getSecret("xaiApiKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("thinkingBudgetTokens") as Promise<number | undefined>,
|
||||
])
|
||||
|
|
@ -2028,6 +2041,8 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
|||
liteLlmBaseUrl,
|
||||
liteLlmModelId,
|
||||
liteLlmApiKey,
|
||||
asksageApiKey,
|
||||
asksageApiUrl,
|
||||
xaiApiKey,
|
||||
},
|
||||
lastShownAnnouncementId,
|
||||
|
|
@ -2173,6 +2188,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
|||
"mistralApiKey",
|
||||
"liteLlmApiKey",
|
||||
"authToken",
|
||||
"asksageApiKey",
|
||||
"xaiApiKey",
|
||||
]
|
||||
for (const key of secretKeys) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export type ApiProvider =
|
|||
| "mistral"
|
||||
| "vscode-lm"
|
||||
| "litellm"
|
||||
| "asksage"
|
||||
| "xai"
|
||||
|
||||
export interface ApiHandlerOptions {
|
||||
|
|
@ -58,6 +59,8 @@ export interface ApiHandlerOptions {
|
|||
vsCodeLmModelSelector?: any
|
||||
o3MiniReasoningEffort?: string
|
||||
qwenApiLine?: string
|
||||
asksageApiUrl?: string
|
||||
asksageApiKey?: string
|
||||
xaiApiKey?: string
|
||||
thinkingBudgetTokens?: number
|
||||
}
|
||||
|
|
@ -808,6 +811,38 @@ export const liteLlmModelInfoSaneDefaults: ModelInfo = {
|
|||
outputPrice: 0,
|
||||
}
|
||||
|
||||
// AskSage Models
|
||||
// https://docs.asksage.ai/
|
||||
export type AskSageModelId = keyof typeof askSageModels
|
||||
export const askSageDefaultModelId: AskSageModelId = "claude-35-sonnet"
|
||||
export const askSageDefaultURL: string = "https://api.asksage.ai/server"
|
||||
export const askSageModels = {
|
||||
"gpt-4o": {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
},
|
||||
"gpt-4o-gov": {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
},
|
||||
"claude-35-sonnet": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
},
|
||||
}
|
||||
|
||||
// X AI
|
||||
// https://docs.x.ai/docs/api-reference
|
||||
export type XAIModelId = keyof typeof xaiModels
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ import {
|
|||
qwenModels,
|
||||
vertexDefaultModelId,
|
||||
vertexModels,
|
||||
askSageModels,
|
||||
askSageDefaultModelId,
|
||||
askSageDefaultURL,
|
||||
xaiDefaultModelId,
|
||||
xaiModels,
|
||||
} from "../../../../src/shared/api"
|
||||
|
|
@ -198,10 +201,40 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
<VSCodeOption value="lmstudio">LM Studio</VSCodeOption>
|
||||
<VSCodeOption value="ollama">Ollama</VSCodeOption>
|
||||
<VSCodeOption value="litellm">LiteLLM</VSCodeOption>
|
||||
<VSCodeOption value="asksage">AskSage</VSCodeOption>
|
||||
<VSCodeOption value="xai">X AI</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
|
||||
{selectedProvider === "asksage" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.asksageApiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
onInput={handleInputChange("asksageApiKey")}
|
||||
placeholder="Enter API Key...">
|
||||
<span style={{ fontWeight: 500 }}>AskSage 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.
|
||||
</p>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.asksageApiUrl || askSageDefaultURL}
|
||||
style={{ width: "100%" }}
|
||||
type="url"
|
||||
onInput={handleInputChange("asksageApiUrl")}
|
||||
placeholder="Enter AskSage API URL...">
|
||||
<span style={{ fontWeight: 500 }}>AskSage API URL</span>
|
||||
</VSCodeTextField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "anthropic" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
|
|
@ -1216,6 +1249,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
|||
{selectedProvider === "deepseek" && createDropdown(deepSeekModels)}
|
||||
{selectedProvider === "qwen" && createDropdown(qwenModels)}
|
||||
{selectedProvider === "mistral" && createDropdown(mistralModels)}
|
||||
{selectedProvider === "asksage" && createDropdown(askSageModels)}
|
||||
{selectedProvider === "xai" && createDropdown(xaiModels)}
|
||||
</DropdownContainer>
|
||||
|
||||
|
|
@ -1433,6 +1467,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
|
|||
return getProviderData(qwenModels, qwenDefaultModelId)
|
||||
case "mistral":
|
||||
return getProviderData(mistralModels, mistralDefaultModelId)
|
||||
case "asksage":
|
||||
return getProviderData(askSageModels, askSageDefaultModelId)
|
||||
case "openrouter":
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
|||
config.qwenApiKey,
|
||||
config.mistralApiKey,
|
||||
config.vsCodeLmModelSelector,
|
||||
config.asksageApiKey,
|
||||
config.xaiApiKey,
|
||||
].some((key) => key !== undefined)
|
||||
: false
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
|
|||
return "You must provide a valid model selector."
|
||||
}
|
||||
break
|
||||
case "asksage":
|
||||
if (!apiConfiguration.asksageApiKey) {
|
||||
return "You must provide a valid API key or choose a different provider."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue