mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-21 12:51:17 -05:00
Remove Kodu provider
This commit is contained in:
@@ -1261,8 +1261,8 @@ ${this.customInstructions.trim()}
|
||||
)
|
||||
const { message, userCredits } = await this.api.createMessage(systemPrompt, adjustedMessages, tools)
|
||||
if (userCredits !== undefined) {
|
||||
console.log("Updating kodu credits", userCredits)
|
||||
this.providerRef.deref()?.updateKoduCredits(userCredits)
|
||||
console.log("Updating credits", userCredits)
|
||||
// TODO: update credits
|
||||
}
|
||||
return message
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ApiConfiguration, ApiModelId, ModelInfo } from "../shared/api"
|
||||
import { AnthropicHandler } from "./anthropic"
|
||||
import { AwsBedrockHandler } from "./bedrock"
|
||||
import { OpenRouterHandler } from "./openrouter"
|
||||
import { KoduHandler } from "./kodu"
|
||||
|
||||
export interface ApiHandlerMessageResponse {
|
||||
message: Anthropic.Messages.Message
|
||||
@@ -38,8 +37,6 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
||||
return new OpenRouterHandler(options)
|
||||
case "bedrock":
|
||||
return new AwsBedrockHandler(options)
|
||||
case "kodu":
|
||||
return new KoduHandler(options)
|
||||
default:
|
||||
return new AnthropicHandler(options)
|
||||
}
|
||||
|
||||
186
src/api/kodu.ts
186
src/api/kodu.ts
@@ -1,186 +0,0 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import axios from "axios"
|
||||
import { ApiHandler, ApiHandlerMessageResponse, withoutImageData } from "."
|
||||
import { ApiHandlerOptions, koduDefaultModelId, KoduModelId, koduModels, ModelInfo } from "../shared/api"
|
||||
import { getKoduCreditsUrl, getKoduInferenceUrl } from "../shared/kodu"
|
||||
|
||||
export async function fetchKoduCredits({ apiKey }: { apiKey: string }) {
|
||||
const response = await axios.get(getKoduCreditsUrl(), {
|
||||
headers: {
|
||||
"x-api-key": apiKey,
|
||||
},
|
||||
})
|
||||
return (response.data.credits as number) || 0
|
||||
}
|
||||
|
||||
export class KoduHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
async createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
tools: Anthropic.Messages.Tool[]
|
||||
): Promise<ApiHandlerMessageResponse> {
|
||||
const modelId = this.getModel().id
|
||||
let requestBody: Anthropic.Beta.PromptCaching.Messages.MessageCreateParamsNonStreaming
|
||||
switch (modelId) {
|
||||
case "claude-3-5-sonnet-20240620":
|
||||
case "claude-3-opus-20240229":
|
||||
case "claude-3-haiku-20240307":
|
||||
const userMsgIndices = messages.reduce(
|
||||
(acc, msg, index) => (msg.role === "user" ? [...acc, index] : acc),
|
||||
[] as number[]
|
||||
)
|
||||
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
|
||||
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
|
||||
requestBody = {
|
||||
model: modelId,
|
||||
max_tokens: this.getModel().info.maxTokens,
|
||||
system: [{ text: systemPrompt, type: "text", cache_control: { type: "ephemeral" } }],
|
||||
messages: messages.map((message, index) => {
|
||||
if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
]
|
||||
: message.content.map((content, contentIndex) =>
|
||||
contentIndex === message.content.length - 1
|
||||
? { ...content, cache_control: { type: "ephemeral" } }
|
||||
: content
|
||||
),
|
||||
}
|
||||
}
|
||||
return message
|
||||
}),
|
||||
tools,
|
||||
tool_choice: { type: "auto" },
|
||||
}
|
||||
break
|
||||
default:
|
||||
requestBody = {
|
||||
model: modelId,
|
||||
max_tokens: this.getModel().info.maxTokens,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages,
|
||||
tools,
|
||||
tool_choice: { type: "auto" },
|
||||
}
|
||||
}
|
||||
|
||||
// const response = await axios.post(getKoduInferenceUrl(), requestBody, {
|
||||
// headers: {
|
||||
// "x-api-key": this.options.koduApiKey,
|
||||
// },
|
||||
// })
|
||||
// const message = response.data
|
||||
// const userCredits = response.headers["user-credits"]
|
||||
// return { message, userCredits: userCredits !== undefined ? parseFloat(userCredits) : undefined }
|
||||
// const thing = {
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// "x-api-key": this.options.koduApiKey || "",
|
||||
// },
|
||||
// body: JSON.stringify(requestBody),
|
||||
// }
|
||||
|
||||
const response = await axios.post(getKoduInferenceUrl(), requestBody, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": this.options.koduApiKey || "",
|
||||
},
|
||||
responseType: "stream",
|
||||
})
|
||||
|
||||
if (response.data) {
|
||||
const reader = response.data
|
||||
const decoder = new TextDecoder("utf-8")
|
||||
let finalResponse: any = null
|
||||
let buffer = ""
|
||||
|
||||
for await (const chunk of reader) {
|
||||
buffer += decoder.decode(chunk, { stream: true })
|
||||
const lines = buffer.split("\n\n")
|
||||
buffer = lines.pop() || ""
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("data: ")) {
|
||||
const eventData = JSON.parse(line.slice(6))
|
||||
|
||||
console.log("eventData", eventData)
|
||||
|
||||
if (eventData.code === 0) {
|
||||
console.log("Health check received")
|
||||
} else if (eventData.code === 1) {
|
||||
finalResponse = eventData.body
|
||||
console.log("finalResponse", finalResponse)
|
||||
break
|
||||
} else if (eventData.code === -1) {
|
||||
throw new Error(`Error in SSE stream: ${JSON.stringify(eventData.json)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (finalResponse) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalResponse) {
|
||||
throw new Error("No final response received from the SSE stream")
|
||||
}
|
||||
|
||||
const message: {
|
||||
anthropic: Anthropic.Messages.Message
|
||||
internal: {
|
||||
userCredits: number
|
||||
}
|
||||
} = finalResponse
|
||||
console.log("message", message)
|
||||
return {
|
||||
message: message.anthropic,
|
||||
userCredits: message.internal?.userCredits,
|
||||
}
|
||||
} else {
|
||||
throw new Error("No response data received")
|
||||
}
|
||||
}
|
||||
|
||||
createUserReadableRequest(
|
||||
userContent: Array<
|
||||
| Anthropic.TextBlockParam
|
||||
| Anthropic.ImageBlockParam
|
||||
| Anthropic.ToolUseBlockParam
|
||||
| Anthropic.ToolResultBlockParam
|
||||
>
|
||||
): any {
|
||||
return {
|
||||
model: this.getModel().id,
|
||||
max_tokens: this.getModel().info.maxTokens,
|
||||
system: "(see SYSTEM_PROMPT in src/ClaudeDev.ts)",
|
||||
messages: [{ conversation_history: "..." }, { role: "user", content: withoutImageData(userContent) }],
|
||||
tools: "(see tools in src/ClaudeDev.ts)",
|
||||
tool_choice: { type: "auto" },
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: KoduModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in koduModels) {
|
||||
const id = modelId as KoduModelId
|
||||
return { id, info: koduModels[id] }
|
||||
}
|
||||
return { id: koduDefaultModelId, info: koduModels[koduDefaultModelId] }
|
||||
}
|
||||
}
|
||||
@@ -109,16 +109,16 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.workspace.registerTextDocumentContentProvider("claude-dev-diff", diffContentProvider)
|
||||
)
|
||||
|
||||
// URI Handler
|
||||
const handleUri = async (uri: vscode.Uri) => {
|
||||
const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
|
||||
const token = query.get("token")
|
||||
const email = query.get("email")
|
||||
if (token) {
|
||||
await sidebarProvider.saveKoduApiKey(token, email || undefined)
|
||||
}
|
||||
}
|
||||
context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
|
||||
// // URI Handler
|
||||
// const handleUri = async (uri: vscode.Uri) => {
|
||||
// const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
|
||||
// const token = query.get("token")
|
||||
// const email = query.get("email")
|
||||
// if (token) {
|
||||
// await sidebarProvider.saveKoduApiKey(token, email || undefined)
|
||||
// }
|
||||
// }
|
||||
// context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
|
||||
}
|
||||
|
||||
// This method is called when your extension is deactivated
|
||||
|
||||
@@ -8,7 +8,6 @@ import { downloadTask, getNonce, getUri, selectImages } from "../utils"
|
||||
import * as path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { HistoryItem } from "../shared/HistoryItem"
|
||||
import { fetchKoduCredits } from "../api/kodu"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -16,19 +15,16 @@ https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default
|
||||
https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts
|
||||
*/
|
||||
|
||||
type SecretKey = "apiKey" | "openRouterApiKey" | "awsAccessKey" | "awsSecretKey" | "koduApiKey"
|
||||
type SecretKey = "apiKey" | "openRouterApiKey" | "awsAccessKey" | "awsSecretKey"
|
||||
type GlobalStateKey =
|
||||
| "apiProvider"
|
||||
| "apiModelId"
|
||||
| "awsRegion"
|
||||
| "koduEmail"
|
||||
| "koduCredits"
|
||||
| "maxRequestsPerTask"
|
||||
| "lastShownAnnouncementId"
|
||||
| "customInstructions"
|
||||
| "alwaysAllowReadOnly"
|
||||
| "taskHistory"
|
||||
| "shouldShowKoduPromo"
|
||||
|
||||
export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
public static readonly sideBarId = "claude-dev.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension.
|
||||
@@ -40,6 +36,28 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
|
||||
constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) {
|
||||
this.outputChannel.appendLine("ClaudeDevProvider instantiated")
|
||||
this.revertKodu()
|
||||
}
|
||||
|
||||
async revertKodu() {
|
||||
const apiProvider = await this.getGlobalState("apiProvider")
|
||||
if (apiProvider === "kodu") {
|
||||
// switch back to previous provider
|
||||
const anthropicKey = await this.getSecret("apiKey")
|
||||
if (anthropicKey) {
|
||||
await this.updateGlobalState("apiProvider", "anthropic" as ApiProvider)
|
||||
} else {
|
||||
const openRouterApiKey = await this.getSecret("openRouterApiKey")
|
||||
if (openRouterApiKey) {
|
||||
await this.updateGlobalState("apiProvider", "openrouter" as ApiProvider)
|
||||
} else {
|
||||
const awsAccessKey = await this.getSecret("awsAccessKey")
|
||||
if (awsAccessKey) {
|
||||
await this.updateGlobalState("apiProvider", "bedrock" as ApiProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -359,25 +377,6 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
case "exportTaskWithId":
|
||||
this.exportTaskWithId(message.text!)
|
||||
break
|
||||
case "didClickKoduSignOut":
|
||||
await this.signOutKodu()
|
||||
break
|
||||
case "fetchKoduCredits":
|
||||
const koduApiKey = await this.getSecret("koduApiKey")
|
||||
if (koduApiKey) {
|
||||
const credits = await fetchKoduCredits({ apiKey: koduApiKey })
|
||||
await this.updateGlobalState("koduCredits", credits)
|
||||
await this.postMessageToWebview({
|
||||
type: "action",
|
||||
action: "koduCreditsFetched",
|
||||
state: await this.getStateToPostToWebview(),
|
||||
})
|
||||
}
|
||||
break
|
||||
case "didDismissKoduPromo":
|
||||
await this.updateGlobalState("shouldShowKoduPromo", false)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "resetState":
|
||||
await this.resetState()
|
||||
break
|
||||
@@ -390,27 +389,6 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
)
|
||||
}
|
||||
|
||||
// Kodu
|
||||
|
||||
async saveKoduApiKey(apiKey: string, email?: string) {
|
||||
await this.storeSecret("koduApiKey", apiKey)
|
||||
await this.updateGlobalState("koduEmail", email)
|
||||
await this.updateGlobalState("apiProvider", "kodu")
|
||||
await this.updateGlobalState("shouldShowKoduPromo", false)
|
||||
await this.postStateToWebview()
|
||||
await this.postMessageToWebview({ type: "action", action: "koduAuthenticated" })
|
||||
this.claudeDev?.updateApi({ apiProvider: "kodu", koduApiKey: apiKey })
|
||||
}
|
||||
|
||||
async signOutKodu() {
|
||||
await this.storeSecret("koduApiKey", undefined)
|
||||
await this.updateGlobalState("koduEmail", undefined)
|
||||
await this.updateGlobalState("koduCredits", undefined)
|
||||
await this.updateGlobalState("apiProvider", "kodu")
|
||||
this.claudeDev?.updateApi({ apiProvider: "kodu", koduApiKey: undefined })
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
|
||||
// Task history
|
||||
|
||||
async getTaskWithId(id: string): Promise<{
|
||||
@@ -510,8 +488,6 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
customInstructions,
|
||||
alwaysAllowReadOnly,
|
||||
taskHistory,
|
||||
koduCredits,
|
||||
shouldShowKoduPromo,
|
||||
} = await this.getState()
|
||||
return {
|
||||
version: this.context.extension?.packageJSON?.version ?? "",
|
||||
@@ -524,8 +500,6 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
claudeMessages: this.claudeDev?.claudeMessages || [],
|
||||
taskHistory: (taskHistory || []).filter((item) => item.ts && item.task).sort((a, b) => b.ts - a.ts),
|
||||
shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
|
||||
koduCredits,
|
||||
shouldShowKoduPromo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,15 +598,11 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
awsAccessKey,
|
||||
awsSecretKey,
|
||||
awsRegion,
|
||||
koduApiKey,
|
||||
koduEmail,
|
||||
koduCredits,
|
||||
maxRequestsPerTask,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
alwaysAllowReadOnly,
|
||||
taskHistory,
|
||||
shouldShowKoduPromo,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<ApiModelId | undefined>,
|
||||
@@ -641,15 +611,11 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
this.getSecret("awsAccessKey") as Promise<string | undefined>,
|
||||
this.getSecret("awsSecretKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("awsRegion") as Promise<string | undefined>,
|
||||
this.getSecret("koduApiKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("koduEmail") as Promise<string | undefined>,
|
||||
this.getGlobalState("koduCredits") as Promise<number | undefined>,
|
||||
this.getGlobalState("maxRequestsPerTask") as Promise<number | undefined>,
|
||||
this.getGlobalState("lastShownAnnouncementId") as Promise<string | undefined>,
|
||||
this.getGlobalState("customInstructions") as Promise<string | undefined>,
|
||||
this.getGlobalState("alwaysAllowReadOnly") as Promise<boolean | undefined>,
|
||||
this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
|
||||
this.getGlobalState("shouldShowKoduPromo") as Promise<boolean | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
@@ -661,8 +627,8 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
if (apiKey) {
|
||||
apiProvider = "anthropic"
|
||||
} else {
|
||||
// New users should default to kodu
|
||||
apiProvider = "kodu"
|
||||
// New users should default to anthropic
|
||||
apiProvider = "anthropic"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,16 +641,12 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
awsAccessKey,
|
||||
awsSecretKey,
|
||||
awsRegion,
|
||||
koduApiKey,
|
||||
koduEmail,
|
||||
},
|
||||
maxRequestsPerTask,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,
|
||||
taskHistory,
|
||||
koduCredits,
|
||||
shouldShowKoduPromo: shouldShowKoduPromo ?? true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,10 +662,6 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
return history
|
||||
}
|
||||
|
||||
async updateKoduCredits(credits: number) {
|
||||
await this.updateGlobalState("koduCredits", credits)
|
||||
}
|
||||
|
||||
// global
|
||||
|
||||
private async updateGlobalState(key: GlobalStateKey, value: any) {
|
||||
@@ -755,7 +713,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
for (const key of this.context.globalState.keys()) {
|
||||
await this.context.globalState.update(key, undefined)
|
||||
}
|
||||
const secretKeys: SecretKey[] = ["apiKey", "openRouterApiKey", "awsAccessKey", "awsSecretKey", "koduApiKey"]
|
||||
const secretKeys: SecretKey[] = ["apiKey", "openRouterApiKey", "awsAccessKey", "awsSecretKey"]
|
||||
for (const key of secretKeys) {
|
||||
await this.storeSecret(key, undefined)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,7 @@ import { HistoryItem } from "./HistoryItem"
|
||||
export interface ExtensionMessage {
|
||||
type: "action" | "state" | "selectedImages"
|
||||
text?: string
|
||||
action?:
|
||||
| "chatButtonTapped"
|
||||
| "settingsButtonTapped"
|
||||
| "historyButtonTapped"
|
||||
| "didBecomeVisible"
|
||||
| "koduAuthenticated"
|
||||
| "koduCreditsFetched"
|
||||
action?: "chatButtonTapped" | "settingsButtonTapped" | "historyButtonTapped" | "didBecomeVisible"
|
||||
state?: ExtensionState
|
||||
images?: string[]
|
||||
}
|
||||
@@ -29,8 +23,6 @@ export interface ExtensionState {
|
||||
claudeMessages: ClaudeMessage[]
|
||||
taskHistory: HistoryItem[]
|
||||
shouldShowAnnouncement: boolean
|
||||
koduCredits?: number
|
||||
shouldShowKoduPromo: boolean
|
||||
}
|
||||
|
||||
export interface ClaudeMessage {
|
||||
|
||||
@@ -16,9 +16,6 @@ export interface WebviewMessage {
|
||||
| "showTaskWithId"
|
||||
| "deleteTaskWithId"
|
||||
| "exportTaskWithId"
|
||||
| "didClickKoduSignOut"
|
||||
| "fetchKoduCredits"
|
||||
| "didDismissKoduPromo"
|
||||
| "resetState"
|
||||
text?: string
|
||||
askResponse?: ClaudeAskResponse
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type ApiProvider = "anthropic" | "openrouter" | "bedrock" | "kodu"
|
||||
export type ApiProvider = "anthropic" | "openrouter" | "bedrock"
|
||||
|
||||
export interface ApiHandlerOptions {
|
||||
apiModelId?: ApiModelId
|
||||
@@ -7,8 +7,6 @@ export interface ApiHandlerOptions {
|
||||
awsAccessKey?: string
|
||||
awsSecretKey?: string
|
||||
awsRegion?: string
|
||||
koduApiKey?: string
|
||||
koduEmail?: string
|
||||
}
|
||||
|
||||
export type ApiConfiguration = ApiHandlerOptions & {
|
||||
@@ -252,10 +250,3 @@ export const openRouterModels = {
|
||||
// outputPrice: 1.5,
|
||||
// },
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
// Kodu
|
||||
export type KoduModelId = keyof typeof koduModels
|
||||
export const koduDefaultModelId: KoduModelId = "claude-3-5-sonnet-20240620"
|
||||
export const koduModels = {
|
||||
...anthropicModels,
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
const KODU_BASE_URL = "https://kodu.ai"
|
||||
|
||||
export function getKoduSignInUrl(uriScheme?: string) {
|
||||
return `${KODU_BASE_URL}/auth/login?redirectTo=${uriScheme}://saoudrizwan.claude-dev&ext=1`
|
||||
}
|
||||
|
||||
export function getKoduAddCreditsUrl(uriScheme?: string) {
|
||||
return `${KODU_BASE_URL}/cloud/plan`
|
||||
}
|
||||
|
||||
export function getKoduCreditsUrl() {
|
||||
return `${KODU_BASE_URL}/api/credits`
|
||||
}
|
||||
|
||||
export function getKoduInferenceUrl() {
|
||||
return `${KODU_BASE_URL}/api/inference-stream`
|
||||
}
|
||||
|
||||
export function getKoduHomepageUrl() {
|
||||
return `${KODU_BASE_URL}`
|
||||
}
|
||||
Reference in New Issue
Block a user