mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add TARS (Tetrate Agent Router Service) as a native provider
- Add TARS provider types and constants - Implement TarsHandler for API integration - Create TARS UI component for settings - Add TARS to provider registrations - Include comprehensive tests for TARS provider Closes #6747
This commit is contained in:
parent
2b647ed9a1
commit
e01730c4e6
11 changed files with 447 additions and 0 deletions
|
|
@ -36,6 +36,7 @@ export const providerNames = [
|
|||
"huggingface",
|
||||
"cerebras",
|
||||
"sambanova",
|
||||
"tars",
|
||||
"zai",
|
||||
"fireworks",
|
||||
] as const
|
||||
|
|
@ -268,6 +269,12 @@ const fireworksSchema = apiModelIdProviderModelSchema.extend({
|
|||
fireworksApiKey: z.string().optional(),
|
||||
})
|
||||
|
||||
const tarsSchema = baseProviderSettingsSchema.extend({
|
||||
tarsApiKey: z.string().optional(),
|
||||
tarsModelId: z.string().optional(),
|
||||
tarsBaseUrl: z.string().optional(),
|
||||
})
|
||||
|
||||
const defaultSchema = z.object({
|
||||
apiProvider: z.undefined(),
|
||||
})
|
||||
|
|
@ -301,6 +308,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
|
|||
litellmSchema.merge(z.object({ apiProvider: z.literal("litellm") })),
|
||||
cerebrasSchema.merge(z.object({ apiProvider: z.literal("cerebras") })),
|
||||
sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })),
|
||||
tarsSchema.merge(z.object({ apiProvider: z.literal("tars") })),
|
||||
zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })),
|
||||
fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })),
|
||||
defaultSchema,
|
||||
|
|
@ -336,6 +344,7 @@ export const providerSettingsSchema = z.object({
|
|||
...litellmSchema.shape,
|
||||
...cerebrasSchema.shape,
|
||||
...sambaNovaSchema.shape,
|
||||
...tarsSchema.shape,
|
||||
...zaiSchema.shape,
|
||||
...fireworksSchema.shape,
|
||||
...codebaseIndexProviderSchema.shape,
|
||||
|
|
@ -363,6 +372,7 @@ export const MODEL_ID_KEYS: Partial<keyof ProviderSettings>[] = [
|
|||
"requestyModelId",
|
||||
"litellmModelId",
|
||||
"huggingFaceModelId",
|
||||
"tarsModelId",
|
||||
]
|
||||
|
||||
export const getModelId = (settings: ProviderSettings): string | undefined => {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export * from "./openai.js"
|
|||
export * from "./openrouter.js"
|
||||
export * from "./requesty.js"
|
||||
export * from "./sambanova.js"
|
||||
export * from "./tars.js"
|
||||
export * from "./unbound.js"
|
||||
export * from "./vertex.js"
|
||||
export * from "./vscode-llm.js"
|
||||
|
|
|
|||
30
packages/types/src/providers/tars.ts
Normal file
30
packages/types/src/providers/tars.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import type { ModelInfo } from "../model.js"
|
||||
|
||||
// TARS is a router service similar to OpenRouter, so we'll follow a similar pattern
|
||||
export const tarsDefaultModelId = "anthropic/claude-3-5-sonnet-20241022"
|
||||
|
||||
export const tarsDefaultModelInfo: ModelInfo = {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsComputerUse: false,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 15.0,
|
||||
cacheWritesPrice: 3.75,
|
||||
cacheReadsPrice: 0.3,
|
||||
description:
|
||||
"Claude 3.5 Sonnet delivers strong performance on tasks requiring visual reasoning, like interpreting charts, graphs, or diagrams. It's a versatile, balanced model that handles both text and image inputs effectively.",
|
||||
}
|
||||
|
||||
export const TARS_DEFAULT_PROVIDER_NAME = "[default]"
|
||||
|
||||
// Models that support prompt caching through TARS
|
||||
export const TARS_PROMPT_CACHING_MODELS = new Set([
|
||||
"anthropic/claude-3-haiku-20240307",
|
||||
"anthropic/claude-3-opus-20240229",
|
||||
"anthropic/claude-3-sonnet-20240229",
|
||||
"anthropic/claude-3-5-haiku-20241022",
|
||||
"anthropic/claude-3-5-sonnet-20240620",
|
||||
"anthropic/claude-3-5-sonnet-20241022",
|
||||
])
|
||||
|
|
@ -32,6 +32,7 @@ import {
|
|||
LiteLLMHandler,
|
||||
ClaudeCodeHandler,
|
||||
SambaNovaHandler,
|
||||
TarsHandler,
|
||||
DoubaoHandler,
|
||||
ZAiHandler,
|
||||
FireworksHandler,
|
||||
|
|
@ -126,6 +127,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
|
|||
return new CerebrasHandler(options)
|
||||
case "sambanova":
|
||||
return new SambaNovaHandler(options)
|
||||
case "tars":
|
||||
return new TarsHandler(options)
|
||||
case "zai":
|
||||
return new ZAiHandler(options)
|
||||
case "fireworks":
|
||||
|
|
|
|||
224
src/api/providers/__tests__/tars.spec.ts
Normal file
224
src/api/providers/__tests__/tars.spec.ts
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
import { describe, it, expect, vitest, beforeEach } from "vitest"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import { tarsDefaultModelId, tarsDefaultModelInfo } from "@roo-code/types"
|
||||
|
||||
import { TarsHandler } from "../tars"
|
||||
import { ApiHandlerOptions } from "../../../shared/api"
|
||||
|
||||
// Mock OpenAI
|
||||
vitest.mock("openai", () => {
|
||||
const mockCreate = vitest.fn()
|
||||
const mockChat = {
|
||||
completions: {
|
||||
create: mockCreate,
|
||||
},
|
||||
}
|
||||
const MockOpenAI = vitest.fn(() => ({
|
||||
chat: mockChat,
|
||||
}))
|
||||
return { default: MockOpenAI }
|
||||
})
|
||||
|
||||
describe("TarsHandler", () => {
|
||||
const mockOptions: ApiHandlerOptions = {
|
||||
tarsApiKey: "test-key",
|
||||
tarsModelId: "anthropic/claude-3-5-sonnet-20241022",
|
||||
tarsBaseUrl: "https://api.tetrate.io/v1",
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("initializes with correct options", () => {
|
||||
const handler = new TarsHandler(mockOptions)
|
||||
expect(handler).toBeInstanceOf(TarsHandler)
|
||||
|
||||
// Verify OpenAI client was initialized with correct parameters
|
||||
expect(OpenAI).toHaveBeenCalledWith({
|
||||
baseURL: "https://api.tetrate.io/v1",
|
||||
apiKey: "test-key",
|
||||
defaultHeaders: expect.any(Object),
|
||||
})
|
||||
})
|
||||
|
||||
it("uses default base URL when not provided", () => {
|
||||
const handler = new TarsHandler({ tarsApiKey: "test-key" })
|
||||
expect(handler).toBeInstanceOf(TarsHandler)
|
||||
|
||||
expect(OpenAI).toHaveBeenCalledWith({
|
||||
baseURL: "https://api.tetrate.io/v1",
|
||||
apiKey: "test-key",
|
||||
defaultHeaders: expect.any(Object),
|
||||
})
|
||||
})
|
||||
|
||||
describe("getModel", () => {
|
||||
it("returns correct model info when options are provided", () => {
|
||||
const handler = new TarsHandler(mockOptions)
|
||||
const result = handler.getModel()
|
||||
|
||||
expect(result).toEqual({
|
||||
id: "anthropic/claude-3-5-sonnet-20241022",
|
||||
info: tarsDefaultModelInfo,
|
||||
})
|
||||
})
|
||||
|
||||
it("returns default model info when options are not provided", () => {
|
||||
const handler = new TarsHandler({})
|
||||
const result = handler.getModel()
|
||||
|
||||
expect(result).toEqual({
|
||||
id: tarsDefaultModelId,
|
||||
info: tarsDefaultModelInfo,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("createMessage", () => {
|
||||
it("generates correct stream chunks", async () => {
|
||||
const mockStream = {
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [{ delta: { content: "Hello" } }],
|
||||
usage: null,
|
||||
}
|
||||
yield {
|
||||
choices: [{ delta: { content: " world" } }],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 2,
|
||||
prompt_tokens_details: { cached_tokens: 5 },
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
const mockOpenAI = vitest.fn(() => ({
|
||||
chat: { completions: { create: mockCreate } },
|
||||
}))
|
||||
;(OpenAI as any).mockImplementation(mockOpenAI)
|
||||
|
||||
const handler = new TarsHandler(mockOptions)
|
||||
|
||||
const chunks = []
|
||||
const generator = handler.createMessage("System prompt", [{ role: "user", content: "Hello" }])
|
||||
|
||||
for await (const chunk of generator) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(chunks).toEqual([
|
||||
{ type: "text", text: "Hello" },
|
||||
{ type: "text", text: " world" },
|
||||
{
|
||||
type: "usage",
|
||||
inputTokens: 10,
|
||||
outputTokens: 2,
|
||||
cacheReadTokens: 5,
|
||||
totalCost: 0,
|
||||
},
|
||||
])
|
||||
|
||||
// The messages will have cache control added
|
||||
expect(mockCreate).toHaveBeenCalledWith({
|
||||
model: "anthropic/claude-3-5-sonnet-20241022",
|
||||
max_tokens: 8192,
|
||||
temperature: 0,
|
||||
messages: expect.arrayContaining([
|
||||
expect.objectContaining({ role: "system" }),
|
||||
expect.objectContaining({ role: "user" }),
|
||||
]),
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
})
|
||||
})
|
||||
|
||||
it("adds cache control for supported models", async () => {
|
||||
const mockStream = {
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [{ delta: { content: "test" } }],
|
||||
usage: null,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
const mockOpenAI = vitest.fn(() => ({
|
||||
chat: { completions: { create: mockCreate } },
|
||||
}))
|
||||
;(OpenAI as any).mockImplementation(mockOpenAI)
|
||||
|
||||
const handler = new TarsHandler({
|
||||
...mockOptions,
|
||||
tarsModelId: "anthropic/claude-3-5-sonnet-20241022",
|
||||
})
|
||||
|
||||
const generator = handler.createMessage("System prompt", [{ role: "user", content: "Hello" }])
|
||||
|
||||
for await (const chunk of generator) {
|
||||
// Consume the generator
|
||||
}
|
||||
|
||||
const call = mockCreate.mock.calls[0][0]
|
||||
|
||||
// The cache breakpoints function should have been called
|
||||
expect(call.messages.length).toBe(2)
|
||||
expect(call.messages[0].role).toBe("system")
|
||||
expect(call.messages[1].role).toBe("user")
|
||||
|
||||
// Messages should have cache control structure
|
||||
expect(call.messages[0].content).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "text",
|
||||
text: "System prompt",
|
||||
cache_control: expect.objectContaining({ type: "ephemeral" }),
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("completePrompt", () => {
|
||||
it("returns correct response", async () => {
|
||||
const mockResponse = { choices: [{ message: { content: "test completion" } }] }
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockResponse)
|
||||
const mockOpenAI = vitest.fn(() => ({
|
||||
chat: { completions: { create: mockCreate } },
|
||||
}))
|
||||
;(OpenAI as any).mockImplementation(mockOpenAI)
|
||||
|
||||
const handler = new TarsHandler(mockOptions)
|
||||
const result = await handler.completePrompt("test prompt")
|
||||
|
||||
expect(result).toBe("test completion")
|
||||
expect(mockCreate).toHaveBeenCalledWith({
|
||||
model: "anthropic/claude-3-5-sonnet-20241022",
|
||||
max_tokens: 8192,
|
||||
temperature: 0,
|
||||
messages: [{ role: "user", content: "test prompt" }],
|
||||
stream: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("handles empty response", async () => {
|
||||
const mockResponse = { choices: [{ message: { content: null } }] }
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockResponse)
|
||||
const mockOpenAI = vitest.fn(() => ({
|
||||
chat: { completions: { create: mockCreate } },
|
||||
}))
|
||||
;(OpenAI as any).mockImplementation(mockOpenAI)
|
||||
|
||||
const handler = new TarsHandler(mockOptions)
|
||||
const result = await handler.completePrompt("test prompt")
|
||||
|
||||
expect(result).toBe("")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -22,6 +22,7 @@ export { OpenAiHandler } from "./openai"
|
|||
export { OpenRouterHandler } from "./openrouter"
|
||||
export { RequestyHandler } from "./requesty"
|
||||
export { SambaNovaHandler } from "./sambanova"
|
||||
export { TarsHandler } from "./tars"
|
||||
export { UnboundHandler } from "./unbound"
|
||||
export { VertexHandler } from "./vertex"
|
||||
export { VsCodeLmHandler } from "./vscode-lm"
|
||||
|
|
|
|||
112
src/api/providers/tars.ts
Normal file
112
src/api/providers/tars.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import { tarsDefaultModelId, tarsDefaultModelInfo, TARS_PROMPT_CACHING_MODELS } from "@roo-code/types"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStreamChunk } from "../transform/stream"
|
||||
import { addCacheBreakpoints as addAnthropicCacheBreakpoints } from "../transform/caching/anthropic"
|
||||
|
||||
import { DEFAULT_HEADERS } from "./constants"
|
||||
import { BaseProvider } from "./base-provider"
|
||||
import type { SingleCompletionHandler } from "../index"
|
||||
|
||||
export class TarsHandler extends BaseProvider implements SingleCompletionHandler {
|
||||
protected options: ApiHandlerOptions
|
||||
private client: OpenAI
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
super()
|
||||
this.options = options
|
||||
|
||||
const baseURL = this.options.tarsBaseUrl || "https://api.tetrate.io/v1"
|
||||
const apiKey = this.options.tarsApiKey ?? "not-provided"
|
||||
|
||||
this.client = new OpenAI({ baseURL, apiKey, defaultHeaders: DEFAULT_HEADERS })
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
): AsyncGenerator<ApiStreamChunk> {
|
||||
const model = this.getModel()
|
||||
|
||||
const { id: modelId, info: modelInfo } = model
|
||||
const maxTokens =
|
||||
this.options.includeMaxTokens !== false && modelInfo.maxTokens ? modelInfo.maxTokens : undefined
|
||||
const temperature = this.options.modelTemperature ?? 0
|
||||
|
||||
// Convert Anthropic messages to OpenAI format
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
// Add prompt caching for supported models
|
||||
if (TARS_PROMPT_CACHING_MODELS.has(modelId)) {
|
||||
addAnthropicCacheBreakpoints(systemPrompt, openAiMessages)
|
||||
}
|
||||
|
||||
const completionParams: OpenAI.Chat.ChatCompletionCreateParams = {
|
||||
model: modelId,
|
||||
...(maxTokens && { max_tokens: maxTokens }),
|
||||
temperature,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
}
|
||||
|
||||
const stream = await this.client.chat.completions.create(completionParams)
|
||||
|
||||
let lastUsage: OpenAI.CompletionUsage | undefined = undefined
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
|
||||
if (delta?.content) {
|
||||
yield { type: "text", text: delta.content }
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
lastUsage = chunk.usage
|
||||
}
|
||||
}
|
||||
|
||||
if (lastUsage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: lastUsage.prompt_tokens || 0,
|
||||
outputTokens: lastUsage.completion_tokens || 0,
|
||||
cacheReadTokens: lastUsage.prompt_tokens_details?.cached_tokens,
|
||||
totalCost: 0, // TARS doesn't provide cost information in the API response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override getModel() {
|
||||
const id = this.options.tarsModelId ?? tarsDefaultModelId
|
||||
const info = tarsDefaultModelInfo
|
||||
|
||||
return { id, info }
|
||||
}
|
||||
|
||||
async completePrompt(prompt: string) {
|
||||
const model = this.getModel()
|
||||
const { id: modelId, info: modelInfo } = model
|
||||
const maxTokens = modelInfo.maxTokens
|
||||
const temperature = this.options.modelTemperature ?? 0
|
||||
|
||||
const completionParams: OpenAI.Chat.ChatCompletionCreateParams = {
|
||||
model: modelId,
|
||||
max_tokens: maxTokens,
|
||||
temperature,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
stream: false,
|
||||
}
|
||||
|
||||
const response = await this.client.chat.completions.create(completionParams)
|
||||
return response.choices[0]?.message?.content || ""
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ import {
|
|||
internationalZAiDefaultModelId,
|
||||
mainlandZAiDefaultModelId,
|
||||
fireworksDefaultModelId,
|
||||
tarsDefaultModelId,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
|
|
@ -78,6 +79,7 @@ import {
|
|||
OpenRouter,
|
||||
Requesty,
|
||||
SambaNova,
|
||||
Tars,
|
||||
Unbound,
|
||||
Vertex,
|
||||
VSCodeLM,
|
||||
|
|
@ -319,6 +321,7 @@ const ApiOptions = ({
|
|||
: internationalZAiDefaultModelId,
|
||||
},
|
||||
fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
|
||||
tars: { field: "tarsModelId", default: tarsDefaultModelId },
|
||||
openai: { field: "openAiModelId" },
|
||||
ollama: { field: "ollamaModelId" },
|
||||
lmstudio: { field: "lmStudioModelId" },
|
||||
|
|
@ -543,6 +546,10 @@ const ApiOptions = ({
|
|||
<SambaNova apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
||||
)}
|
||||
|
||||
{selectedProvider === "tars" && (
|
||||
<Tars apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
||||
)}
|
||||
|
||||
{selectedProvider === "zai" && (
|
||||
<ZAi apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export const PROVIDERS = [
|
|||
{ value: "chutes", label: "Chutes AI" },
|
||||
{ value: "litellm", label: "LiteLLM" },
|
||||
{ value: "sambanova", label: "SambaNova" },
|
||||
{ value: "tars", label: "TARS (Tetrate Agent Router Service)" },
|
||||
{ value: "zai", label: "Z AI" },
|
||||
{ value: "fireworks", label: "Fireworks AI" },
|
||||
].sort((a, b) => a.label.localeCompare(b.label))
|
||||
|
|
|
|||
57
webview-ui/src/components/settings/providers/Tars.tsx
Normal file
57
webview-ui/src/components/settings/providers/Tars.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { useCallback } from "react"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import { type ProviderSettings, tarsDefaultModelId } from "@roo-code/types"
|
||||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
|
||||
import { inputEventTransform } from "../transforms"
|
||||
|
||||
type TarsProps = {
|
||||
apiConfiguration: ProviderSettings
|
||||
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
|
||||
}
|
||||
|
||||
export const Tars = ({ apiConfiguration, setApiConfigurationField }: TarsProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
<K extends keyof ProviderSettings, E>(
|
||||
field: K,
|
||||
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
|
||||
) =>
|
||||
(event: E | Event) => {
|
||||
setApiConfigurationField(field, transform(event as E))
|
||||
},
|
||||
[setApiConfigurationField],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.tarsApiKey || ""}
|
||||
type="password"
|
||||
onInput={handleInputChange("tarsApiKey")}
|
||||
placeholder={t("settings:placeholders.apiKey")}
|
||||
className="w-full">
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<label className="block font-medium">{t("settings:providers.tarsApiKey")}</label>
|
||||
</div>
|
||||
</VSCodeTextField>
|
||||
<div className="text-sm text-vscode-descriptionForeground -mt-2">
|
||||
{t("settings:providers.apiKeyStorageNotice")}
|
||||
</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground">{t("settings:providers.tarsDescription")}</div>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.tarsModelId || tarsDefaultModelId}
|
||||
onInput={handleInputChange("tarsModelId")}
|
||||
placeholder={tarsDefaultModelId}
|
||||
className="w-full">
|
||||
<label className="block font-medium mb-1">{t("settings:providers.model")}</label>
|
||||
</VSCodeTextField>
|
||||
<div className="text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:providers.tarsModelDescription")}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ export { OpenAICompatible } from "./OpenAICompatible"
|
|||
export { OpenRouter } from "./OpenRouter"
|
||||
export { Requesty } from "./Requesty"
|
||||
export { SambaNova } from "./SambaNova"
|
||||
export { Tars } from "./Tars"
|
||||
export { Unbound } from "./Unbound"
|
||||
export { Vertex } from "./Vertex"
|
||||
export { VSCodeLM } from "./VSCodeLM"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue