diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 30521f2c685..6a1100ec6c5 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -166,6 +166,7 @@ export const SECRET_STATE_KEYS = [ "xaiApiKey", "groqApiKey", "chutesApiKey", + "archgwApiKey", "litellmApiKey", "codeIndexOpenAiKey", "codeIndexQdrantApiKey", diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 884337767fe..4b785c52c29 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -24,6 +24,7 @@ export const providerNames = [ "mistral", "moonshot", "deepseek", + "archgw", "unbound", "requesty", "human-relay", @@ -195,6 +196,14 @@ const moonshotSchema = apiModelIdProviderModelSchema.extend({ moonshotApiKey: z.string().optional(), }) +const archgwSchema = apiModelIdProviderModelSchema.extend({ + archgwBaseUrl: z.string().optional(), + archgwApiKey: z.string().optional(), + archgwModelId: z.string().optional(), + archgwUsePreferences: z.boolean().optional(), + archgwPreferenceConfig: z.string().optional(), +}) + const unboundSchema = baseProviderSettingsSchema.extend({ unboundApiKey: z.string().optional(), unboundModelId: z.string().optional(), @@ -250,6 +259,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })), deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })), moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })), + archgwSchema.merge(z.object({ apiProvider: z.literal("archgw") })), unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })), requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })), humanRelaySchema.merge(z.object({ apiProvider: z.literal("human-relay") })), @@ -279,6 +289,7 @@ export const providerSettingsSchema = z.object({ ...mistralSchema.shape, ...deepSeekSchema.shape, ...moonshotSchema.shape, + ...archgwSchema.shape, ...unboundSchema.shape, ...requestySchema.shape, ...humanRelaySchema.shape, @@ -304,6 +315,7 @@ export const MODEL_ID_KEYS: Partial[] = [ "unboundModelId", "requestyModelId", "litellmModelId", + "archgwModelId", ] export const getModelId = (settings: ProviderSettings): string | undefined => { diff --git a/packages/types/src/providers/archgw.ts b/packages/types/src/providers/archgw.ts new file mode 100644 index 00000000000..c4eb36fb958 --- /dev/null +++ b/packages/types/src/providers/archgw.ts @@ -0,0 +1,13 @@ +import type { ModelInfo } from "../model.js" + +export const archgwDefaultModelId = "openai/gpt-4.1" + +export const archgwDefaultModelInfo: ModelInfo = { + maxTokens: 32_768, + contextWindow: 1_047_576, + supportsImages: true, + supportsPromptCache: true, + inputPrice: 2, + outputPrice: 8, + cacheReadsPrice: 0.5, +} diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index e4e506b8a7b..dfd6f2f2ee0 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -18,3 +18,4 @@ export * from "./unbound.js" export * from "./vertex.js" export * from "./vscode-llm.js" export * from "./xai.js" +export * from "./archgw.js" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5030055feac..5f94883f7f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1060,6 +1060,9 @@ importers: vscrui: specifier: ^0.2.2 version: 0.2.2(@types/react@18.3.23)(react@18.3.1) + yaml: + specifier: ^2.8.0 + version: 2.8.0 zod: specifier: ^3.25.61 version: 3.25.61 @@ -13295,7 +13298,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.29)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) '@vitest/utils@3.2.4': dependencies: diff --git a/scripts/update-contributors.js b/scripts/update-contributors.js index 6bd4c35f0ca..64ade5bea20 100644 --- a/scripts/update-contributors.js +++ b/scripts/update-contributors.js @@ -183,14 +183,14 @@ async function readReadme() { * @param {Array} contributors Array of contributor objects from GitHub API * @returns {string} HTML for contributors section */ -const EXCLUDED_LOGIN_SUBSTRINGS = ['[bot]', 'R00-B0T']; -const EXCLUDED_LOGIN_EXACTS = ['cursor', 'roomote']; +const EXCLUDED_LOGIN_SUBSTRINGS = ["[bot]", "R00-B0T"] +const EXCLUDED_LOGIN_EXACTS = ["cursor", "roomote"] function formatContributorsSection(contributors) { // Filter out GitHub Actions bot, cursor, and roomote - const filteredContributors = contributors.filter((c) => - !EXCLUDED_LOGIN_SUBSTRINGS.some(sub => c.login.includes(sub)) && - !EXCLUDED_LOGIN_EXACTS.includes(c.login) + const filteredContributors = contributors.filter( + (c) => + !EXCLUDED_LOGIN_SUBSTRINGS.some((sub) => c.login.includes(sub)) && !EXCLUDED_LOGIN_EXACTS.includes(c.login), ) // Start building with Markdown table format diff --git a/src/api/index.ts b/src/api/index.ts index 4598a711b2a..c935f33b7ce 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -28,6 +28,7 @@ import { GroqHandler, ChutesHandler, LiteLLMHandler, + ArchGwHandler, ClaudeCodeHandler, } from "./providers" @@ -92,6 +93,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { return new DeepSeekHandler(options) case "moonshot": return new MoonshotHandler(options) + case "archgw": + return new ArchGwHandler(options) case "vscode-lm": return new VsCodeLmHandler(options) case "mistral": diff --git a/src/api/providers/__tests__/archgw.spec.ts b/src/api/providers/__tests__/archgw.spec.ts new file mode 100644 index 00000000000..e7e249da3c5 --- /dev/null +++ b/src/api/providers/__tests__/archgw.spec.ts @@ -0,0 +1,214 @@ +// npx vitest run src/api/providers/__tests__/archgw.spec.ts + +import { Anthropic } from "@anthropic-ai/sdk" +import { ArchGwHandler } from "../archgw" +import { ApiHandlerOptions } from "../../../shared/api" + +const mockCreate = vitest.fn() + +vitest.mock("openai", () => { + return { + __esModule: true, + default: vitest.fn().mockImplementation(() => ({ + chat: { + completions: { + create: mockCreate.mockImplementation((options) => { + if (!options.stream) { + return Promise.resolve({ + id: "test-completion", + choices: [ + { + message: { role: "assistant", content: "ArchGW response" }, + finish_reason: "stop", + index: 0, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + }, + }) + } + + // Streaming: return an object with withResponse() method + return { + withResponse: () => ({ + data: { + [Symbol.asyncIterator]: async function* () { + yield { + choices: [ + { + delta: { content: "ArchGW stream" }, + index: 0, + }, + ], + usage: null, + } + yield { + choices: [ + { + delta: {}, + index: 0, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + }, + } + }, + }, + }), + } + }), + }, + }, + })), + } +}) + +describe("ArchGwHandler", () => { + let handler: ArchGwHandler + let mockOptions: ApiHandlerOptions + + beforeEach(() => { + mockOptions = { + archgwModelId: "arch-model", + archgwBaseUrl: "http://localhost:12000/v1", + archgwApiKey: "test-key", + archgwPreferenceConfig: "test-pref", + } + handler = new ArchGwHandler(mockOptions) + mockCreate.mockClear() + }) + + describe("constructor", () => { + it("should initialize with provided options", () => { + expect(handler).toBeInstanceOf(ArchGwHandler) + expect(handler.preferenceConfig).toBe("test-pref") + }) + + it("should use default base URL if not provided", () => { + const handlerWithoutUrl = new ArchGwHandler({ + archgwModelId: "arch-model", + archgwApiKey: "test-key", + }) + expect(handlerWithoutUrl).toBeInstanceOf(ArchGwHandler) + }) + + it("should not set preferenceConfig if not provided", () => { + const handlerNoPref = new ArchGwHandler({ + archgwModelId: "arch-model", + archgwApiKey: "test-key", + }) + expect(handlerNoPref.preferenceConfig).toBeUndefined() + }) + + it("should set preferenceConfig to empty string if provided as empty", () => { + const handlerEmptyPref = new ArchGwHandler({ + archgwModelId: "arch-model", + archgwApiKey: "test-key", + archgwPreferenceConfig: "", + }) + expect(handlerEmptyPref.preferenceConfig).toBe("") + }) + }) + + describe("createMessage", () => { + const systemPrompt = "You are a helpful assistant." + const messages: Anthropic.Messages.MessageParam[] = [ + { + role: "user", + content: "Hello!", + }, + ] + + it("should handle streaming responses", async () => { + const stream = handler.createMessage(systemPrompt, messages) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(chunks.length).toBeGreaterThan(0) + const textChunks = chunks.filter((chunk) => chunk.type === "text") + expect(textChunks).toHaveLength(1) + expect(textChunks[0].text).toBe("ArchGW stream") + }) + + it("should handle API errors", async () => { + mockCreate.mockImplementationOnce(() => ({ + withResponse: () => { + throw new Error("API Error") + }, + })) + + const stream = handler.createMessage(systemPrompt, messages) + + await expect(async () => { + for await (const _chunk of stream) { + // Should not reach here + } + }).rejects.toThrow("archgw streaming error: API Error") + }) + }) + + describe("completePrompt", () => { + it("should complete prompt successfully", async () => { + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("ArchGW response") + expect(mockCreate).toHaveBeenCalledWith({ + model: mockOptions.archgwModelId, + messages: [{ role: "user", content: "Test prompt" }], + temperature: 0, + max_tokens: 32768, + metadata: { archgw_preference_config: "test-pref" }, + }) + }) + + it("should not include archgw_preference_config in metadata if preferenceConfig is not set", async () => { + const handlerNoPref = new ArchGwHandler({ + archgwModelId: "arch-model", + archgwApiKey: "test-key", + }) + mockCreate.mockClear() + await handlerNoPref.completePrompt("Test prompt") + const call = mockCreate.mock.calls[0][0] + expect(call.metadata).toBeUndefined() + }) + + it("should include archgw_preference_config in metadata if preferenceConfig is empty string", async () => { + const preferenceConfig = `- name: code generation + model: gpt-4o-mini + usage: generating new code snippets +- name: code understanding + model: gpt-4.1 + usage: understand and explain existing code snippets +` + const handlerEmptyPref = new ArchGwHandler({ + archgwModelId: "arch-model", + archgwApiKey: "test-key", + archgwPreferenceConfig: preferenceConfig, + }) + mockCreate.mockClear() + await handlerEmptyPref.completePrompt("Test prompt") + const call = mockCreate.mock.calls[0][0] + expect(call.metadata).toEqual({ archgw_preference_config: preferenceConfig }) + }) + + it("should handle API errors", async () => { + mockCreate.mockRejectedValueOnce(new Error("API Error")) + await expect(handler.completePrompt("Test prompt")).rejects.toThrow("archgw completion error: API Error") + }) + + it("should handle empty response", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [{ message: { content: "" } }], + }) + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("") + }) + }) +}) diff --git a/src/api/providers/archgw.ts b/src/api/providers/archgw.ts new file mode 100644 index 00000000000..91ec24f0281 --- /dev/null +++ b/src/api/providers/archgw.ts @@ -0,0 +1,158 @@ +import OpenAI from "openai" +import { Anthropic } from "@anthropic-ai/sdk" // Keep for type usage only + +import { archgwDefaultModelId, archgwDefaultModelInfo } from "@roo-code/types" + +import { calculateApiCostOpenAI } from "../../shared/cost" + +import { ApiHandlerOptions } from "../../shared/api" + +import { ApiStream, ApiStreamUsageChunk } from "../transform/stream" +import { convertToOpenAiMessages } from "../transform/openai-format" + +import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" +import { RouterProvider } from "./router-provider" + +/** + * ArchGw provider handler + * + * This handler uses the ArchGw API to proxy requests to various LLM providers. + * It follows the OpenAI API format for compatibility. + */ +export class ArchGwHandler extends RouterProvider implements SingleCompletionHandler { + preferenceConfig?: string // Declare the property + archgwUsePreferences: boolean + + constructor(options: ApiHandlerOptions) { + console.log("ArchGwHandler constructor called with options:", options) + super({ + options, + name: "archgw", + baseURL: options.archgwBaseUrl || "http://localhost:12000/v1", + apiKey: options.archgwApiKey, + modelId: options.archgwModelId, + defaultModelId: options.archgwModelId || archgwDefaultModelId, + defaultModelInfo: archgwDefaultModelInfo, + }) + this.preferenceConfig = options.archgwPreferenceConfig // Store the new parameter + this.archgwUsePreferences = options.archgwUsePreferences || false // Store the preference flag + } + + override async *createMessage( + systemPrompt: string, + messages: Anthropic.Messages.MessageParam[], + metadata?: ApiHandlerCreateMessageMetadata, + ): ApiStream { + const { id: modelId, info } = await this.fetchModel() + + const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ + { role: "system", content: systemPrompt }, + ...convertToOpenAiMessages(messages), + ] + + // Required by some providers; others default to max tokens allowed + let maxTokens: number | undefined = info.maxTokens ?? undefined + + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { + model: modelId, + max_tokens: maxTokens, + messages: openAiMessages, + stream: true, + stream_options: { + include_usage: true, + }, + } + + if (this.archgwUsePreferences && this.preferenceConfig) { + if (!requestOptions.metadata) { + requestOptions.metadata = {} + } + requestOptions.metadata["archgw_preference_config"] = this.preferenceConfig + } + + if (this.supportsTemperature(modelId)) { + requestOptions.temperature = this.options.modelTemperature ?? 0 + } + + try { + const { data: completion } = await this.client.chat.completions.create(requestOptions).withResponse() + + let lastUsage + + for await (const chunk of completion) { + const delta = chunk.choices[0]?.delta + const usage = chunk.usage as ArchgwUsage + + if (delta?.content) { + yield { type: "text", text: delta.content } + } + + if (usage) { + lastUsage = usage + } + } + + if (lastUsage) { + const usageData: ApiStreamUsageChunk = { + type: "usage", + inputTokens: lastUsage.prompt_tokens || 0, + outputTokens: lastUsage.completion_tokens || 0, + cacheWriteTokens: lastUsage.cache_creation_input_tokens || 0, + cacheReadTokens: lastUsage.prompt_tokens_details?.cached_tokens || 0, + } + + usageData.totalCost = calculateApiCostOpenAI( + info, + usageData.inputTokens, + usageData.outputTokens, + usageData.cacheWriteTokens, + usageData.cacheReadTokens, + ) + + yield usageData + } + } catch (error) { + if (error instanceof Error) { + throw new Error(`archgw streaming error: ${error.message}`) + } + throw error + } + } + + async completePrompt(prompt: string): Promise { + const { id: modelId, info } = await this.fetchModel() + + try { + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { + model: modelId, + messages: [{ role: "user", content: prompt }], + } + + if (this.supportsTemperature(modelId)) { + requestOptions.temperature = this.options.modelTemperature ?? 0 + } + + requestOptions.max_tokens = info.maxTokens + + if (this.preferenceConfig) { + if (!requestOptions.metadata) { + requestOptions.metadata = {} + } + requestOptions.metadata["archgw_preference_config"] = this.preferenceConfig + } + + const response = await this.client.chat.completions.create(requestOptions) + return response.choices[0]?.message.content || "" + } catch (error) { + if (error instanceof Error) { + throw new Error(`archgw completion error: ${error.message}`) + } + throw error + } + } +} + +// archgw usage may include an extra field for Anthropic use cases. +interface ArchgwUsage extends OpenAI.CompletionUsage { + cache_creation_input_tokens?: number +} diff --git a/src/api/providers/fetchers/__tests__/archgw.spec.ts b/src/api/providers/fetchers/__tests__/archgw.spec.ts new file mode 100644 index 00000000000..3737f5a0130 --- /dev/null +++ b/src/api/providers/fetchers/__tests__/archgw.spec.ts @@ -0,0 +1,124 @@ +// Mocks must come first, before imports +vi.mock("axios") + +import type { Mock } from "vitest" +import axios from "axios" +import { getArchGwModels } from "../archgw" +import { parseApiPrice } from "../../../../shared/cost" + +const mockedAxios = axios as typeof axios & { + get: Mock + isAxiosError: Mock +} + +describe("getArchGwModels", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("fetches and formats archgw models with all capabilities and prices", async () => { + const mockResponse = { + data: { + data: [ + { + id: "test-model-1", + maxTokensOutput: 4096, + maxTokensInput: 200000, + capabilities: ["input:image", "computer_use", "caching"], + pricePerToken: { + input: "0.00001", + output: "0.00002", + cacheWrite: "0.00003", + cacheRead: "0.00004", + }, + }, + { + id: "test-model-2", + maxTokensOutput: 8192, + maxTokensInput: 128000, + capabilities: [], + pricePerToken: {}, + }, + ], + }, + } + + mockedAxios.get.mockResolvedValue(mockResponse) + + const result = await getArchGwModels("http://localhost:5000") + + expect(mockedAxios.get).toHaveBeenCalledWith( + "http://localhost:5000/v1/models", + expect.objectContaining({ + headers: expect.objectContaining({ + "Content-Type": "application/json", + }), + timeout: 5000, + }), + ) + + expect(result).toEqual({ + "test-model-1": { + maxTokens: 4096, + contextWindow: 200000, + supportsImages: true, + supportsComputerUse: true, + supportsPromptCache: true, + inputPrice: parseApiPrice("0.00001"), + outputPrice: parseApiPrice("0.00002"), + description: undefined, + cacheWritesPrice: parseApiPrice("0.00003"), + cacheReadsPrice: parseApiPrice("0.00004"), + }, + "test-model-2": { + maxTokens: 8192, + contextWindow: 128000, + supportsImages: false, + supportsComputerUse: false, + supportsPromptCache: false, + inputPrice: parseApiPrice(undefined), + outputPrice: parseApiPrice(undefined), + description: undefined, + cacheWritesPrice: parseApiPrice(undefined), + cacheReadsPrice: parseApiPrice(undefined), + }, + }) + }) + + it("handles base URLs with trailing slashes", async () => { + const mockResponse = { data: { data: [] } } + mockedAxios.get.mockResolvedValue(mockResponse) + + await getArchGwModels("http://localhost:5000/") + + expect(mockedAxios.get).toHaveBeenCalledWith("http://localhost:5000/v1/models", expect.anything()) + }) + + it("returns empty object when data array is empty", async () => { + const mockResponse = { data: { data: [] } } + mockedAxios.get.mockResolvedValue(mockResponse) + + const result = await getArchGwModels("http://localhost:5000") + expect(result).toEqual({}) + }) + + it("returns empty object and logs error for axios/network error", async () => { + const axiosError = new Error("Network error") + mockedAxios.get.mockRejectedValue(axiosError) + + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + const result = await getArchGwModels("http://localhost:5000") + expect(result).toEqual({}) + expect(consoleSpy).toHaveBeenCalled() + consoleSpy.mockRestore() + }) + + it("uses timeout parameter in axios request", async () => { + const mockResponse = { data: { data: [] } } + mockedAxios.get.mockResolvedValue(mockResponse) + + await getArchGwModels("http://localhost:5000") + + expect(mockedAxios.get).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ timeout: 5000 })) + }) +}) diff --git a/src/api/providers/fetchers/archgw.ts b/src/api/providers/fetchers/archgw.ts new file mode 100644 index 00000000000..51784ded8b0 --- /dev/null +++ b/src/api/providers/fetchers/archgw.ts @@ -0,0 +1,52 @@ +import axios from "axios" + +import type { ModelInfo } from "@roo-code/types" + +import { parseApiPrice } from "../../../shared/cost" + +export async function getArchGwModels(baseUrl: string): Promise> { + const models: Record = {} + + console.log("Fetching archgw models...") + + try { + const headers: Record = { + "Content-Type": "application/json", + } + + const url = new URL("/v1/models", baseUrl).href + const response = await axios.get(url, { headers, timeout: 5000 }) + const rawModels = response.data + + for (const rawModel of rawModels.data) { + const modelInfo: ModelInfo = { + maxTokens: rawModel.maxTokensOutput, + contextWindow: rawModel.maxTokensInput, + supportsImages: rawModel.capabilities?.includes("input:image"), + supportsComputerUse: rawModel.capabilities?.includes("computer_use"), + supportsPromptCache: rawModel.capabilities?.includes("caching"), + inputPrice: parseApiPrice(rawModel.pricePerToken?.input), + outputPrice: parseApiPrice(rawModel.pricePerToken?.output), + description: undefined, + cacheWritesPrice: parseApiPrice(rawModel.pricePerToken?.cacheWrite), + cacheReadsPrice: parseApiPrice(rawModel.pricePerToken?.cacheRead), + } + + switch (rawModel.id) { + case rawModel.id.startsWith("anthropic/"): + modelInfo.maxTokens = 8192 + break + default: + break + } + + models[rawModel.id] = modelInfo + } + } catch (error) { + console.error(`Error fetching archgw models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`) + } + + console.log("Fetched archgw models:", models) + + return models +} diff --git a/src/api/providers/fetchers/modelCache.ts b/src/api/providers/fetchers/modelCache.ts index fef700268dc..6904e90b34d 100644 --- a/src/api/providers/fetchers/modelCache.ts +++ b/src/api/providers/fetchers/modelCache.ts @@ -15,6 +15,7 @@ import { getGlamaModels } from "./glama" import { getUnboundModels } from "./unbound" import { getLiteLLMModels } from "./litellm" import { GetModelsOptions } from "../../../shared/api" +import { getArchGwModels } from "./archgw" import { getOllamaModels } from "./ollama" import { getLMStudioModels } from "./lmstudio" @@ -72,6 +73,9 @@ export const getModels = async (options: GetModelsOptions): Promise // Type safety ensures apiKey and baseUrl are always provided for litellm models = await getLiteLLMModels(options.apiKey, options.baseUrl) break + case "archgw": + models = await getArchGwModels(options.baseUrl) + break case "ollama": models = await getOllamaModels(options.baseUrl) break diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index 89d4c203adf..932b7ce04a0 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -22,3 +22,4 @@ export { UnboundHandler } from "./unbound" export { VertexHandler } from "./vertex" export { VsCodeLmHandler } from "./vscode-lm" export { XAIHandler } from "./xai" +export { ArchGwHandler } from "./archgw" diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index 5272c334510..5a5396926b8 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -2416,10 +2416,10 @@ describe("ClineProvider - Router Models", () => { litellm: mockModels, ollama: {}, lmstudio: {}, + archgw: mockModels, }, }) }) - test("handles requestRouterModels with individual provider failures", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] @@ -2432,6 +2432,8 @@ describe("ClineProvider - Router Models", () => { unboundApiKey: "unbound-key", litellmApiKey: "litellm-key", litellmBaseUrl: "http://localhost:4000", + // archgwApiKey: "archgw-key", + // archgwBaseUrl: "http://localhost:12000/v1", }, } as any) @@ -2447,6 +2449,7 @@ describe("ClineProvider - Router Models", () => { .mockResolvedValueOnce(mockModels) // glama success .mockRejectedValueOnce(new Error("Unbound API error")) // unbound fail .mockRejectedValueOnce(new Error("LiteLLM connection failed")) // litellm fail + .mockResolvedValueOnce(mockModels) // archgw success await messageHandler({ type: "requestRouterModels" }) @@ -2461,6 +2464,7 @@ describe("ClineProvider - Router Models", () => { ollama: {}, lmstudio: {}, litellm: {}, + archgw: mockModels, }, }) @@ -2571,6 +2575,7 @@ describe("ClineProvider - Router Models", () => { litellm: {}, ollama: {}, lmstudio: {}, + archgw: mockModels, }, }) }) diff --git a/src/core/webview/__tests__/webviewMessageHandler.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.spec.ts index 284ee989444..cf99715b9fa 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.spec.ts @@ -153,6 +153,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { litellm: mockModels, ollama: {}, lmstudio: {}, + archgw: mockModels, }, }) }) @@ -240,6 +241,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { litellm: {}, ollama: {}, lmstudio: {}, + archgw: mockModels, }, }) }) @@ -277,6 +279,7 @@ describe("webviewMessageHandler - requestRouterModels", () => { litellm: {}, ollama: {}, lmstudio: {}, + archgw: mockModels, }, }) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 780d40df891..29dccbae7fc 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -520,6 +520,7 @@ export const webviewMessageHandler = async ( glama: {}, unbound: {}, litellm: {}, + archgw: {}, ollama: {}, lmstudio: {}, } @@ -555,6 +556,15 @@ export const webviewMessageHandler = async ( }) } + const archgwBaseUrl = + apiConfiguration.archgwBaseUrl || message?.values?.archgwBaseUrl || "http://localhost:12000/v1" + if (archgwBaseUrl) { + modelFetchPromises.push({ + key: "archgw", + options: { provider: "archgw", baseUrl: archgwBaseUrl }, + }) + } + const results = await Promise.allSettled( modelFetchPromises.map(async ({ key, options }) => { const models = await safeGetModels(options) diff --git a/src/shared/api.ts b/src/shared/api.ts index 8cbfc721336..b1cc2166040 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -11,7 +11,7 @@ export type ApiHandlerOptions = Omit // RouterName -const routerNames = ["openrouter", "requesty", "glama", "unbound", "litellm", "ollama", "lmstudio"] as const +const routerNames = ["openrouter", "requesty", "glama", "unbound", "litellm", "ollama", "lmstudio", "archgw"] as const export type RouterName = (typeof routerNames)[number] @@ -113,3 +113,4 @@ export type GetModelsOptions = | { provider: "litellm"; apiKey: string; baseUrl: string } | { provider: "ollama"; baseUrl?: string } | { provider: "lmstudio"; baseUrl?: string } + | { provider: "archgw"; baseUrl: string } diff --git a/webview-ui/package.json b/webview-ui/package.json index 4c6edc7a2bf..942104eb7d4 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -76,6 +76,7 @@ "use-sound": "^5.0.0", "vscode-material-icons": "^0.1.1", "vscrui": "^0.2.2", + "yaml": "^2.8.0", "zod": "^3.25.61" }, "devDependencies": { diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 6c6c621956c..a377cc12120 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -25,6 +25,7 @@ import { chutesDefaultModelId, bedrockDefaultModelId, vertexDefaultModelId, + archgwDefaultModelId, } from "@roo-code/types" import { vscode } from "@src/utils/vscode" @@ -56,6 +57,7 @@ import { Chutes, ClaudeCode, DeepSeek, + ArchGw, Gemini, Glama, Groq, @@ -200,6 +202,8 @@ const ApiOptions = ({ vscode.postMessage({ type: "requestVsCodeLmModels" }) } else if (selectedProvider === "litellm") { vscode.postMessage({ type: "requestRouterModels" }) + } else if (selectedProvider === "archgw") { + vscode.postMessage({ type: "requestRouterModels" }) } }, 250, @@ -212,6 +216,7 @@ const ApiOptions = ({ apiConfiguration?.lmStudioBaseUrl, apiConfiguration?.litellmBaseUrl, apiConfiguration?.litellmApiKey, + apiConfiguration?.archgwBaseUrl, customHeaders, ], ) @@ -299,6 +304,7 @@ const ApiOptions = ({ openai: { field: "openAiModelId" }, ollama: { field: "ollamaModelId" }, lmstudio: { field: "lmStudioModelId" }, + archgw: { field: "archgwModelId", default: archgwDefaultModelId }, } const config = PROVIDER_MODEL_CONFIG[value] @@ -471,6 +477,14 @@ const ApiOptions = ({ )} + {selectedProvider === "archgw" && ( + + )} + {selectedProvider === "vscode-lm" && ( )} diff --git a/webview-ui/src/components/settings/ModelPicker.tsx b/webview-ui/src/components/settings/ModelPicker.tsx index 5882d4f3f3a..e185935d38a 100644 --- a/webview-ui/src/components/settings/ModelPicker.tsx +++ b/webview-ui/src/components/settings/ModelPicker.tsx @@ -27,7 +27,13 @@ import { ApiErrorMessage } from "./ApiErrorMessage" type ModelIdKey = keyof Pick< ProviderSettings, - "glamaModelId" | "openRouterModelId" | "unboundModelId" | "requestyModelId" | "openAiModelId" | "litellmModelId" + | "glamaModelId" + | "openRouterModelId" + | "unboundModelId" + | "requestyModelId" + | "openAiModelId" + | "litellmModelId" + | "archgwModelId" > interface ModelPickerProps { @@ -39,6 +45,7 @@ interface ModelPickerProps { apiConfiguration: ProviderSettings setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void organizationAllowList: OrganizationAllowList + showModelInfoDescription?: boolean errorMessage?: string } @@ -51,6 +58,7 @@ export const ModelPicker = ({ apiConfiguration, setApiConfigurationField, organizationAllowList, + showModelInfoDescription, errorMessage, }: ModelPickerProps) => { const { t } = useAppTranslation() @@ -216,16 +224,20 @@ export const ModelPicker = ({ setIsDescriptionExpanded={setIsDescriptionExpanded} /> )} -
- , - defaultModelLink: onSelect(defaultModelId)} className="text-sm" />, - }} - values={{ serviceName, defaultModelId }} - /> -
+ {showModelInfoDescription !== false && ( +
+ , + defaultModelLink: ( + onSelect(defaultModelId)} className="text-sm" /> + ), + }} + values={{ serviceName, defaultModelId }} + /> +
+ )} ) } diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index 1140e4c0bcf..85f046bc85c 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -53,4 +53,5 @@ export const PROVIDERS = [ { value: "groq", label: "Groq" }, { value: "chutes", label: "Chutes AI" }, { value: "litellm", label: "LiteLLM" }, + { value: "archgw", label: "Arch LLM Gateway" }, ].sort((a, b) => a.label.localeCompare(b.label)) diff --git a/webview-ui/src/components/settings/providers/ArchGw.tsx b/webview-ui/src/components/settings/providers/ArchGw.tsx new file mode 100644 index 00000000000..723577c6b60 --- /dev/null +++ b/webview-ui/src/components/settings/providers/ArchGw.tsx @@ -0,0 +1,231 @@ +import { useCallback, useState, useEffect, useRef, useMemo } from "react" +import { VSCodeTextArea, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" +import { Checkbox } from "vscrui" + +import { type ProviderSettings, type OrganizationAllowList, archgwDefaultModelId } from "@roo-code/types" + +import { RouterName } from "@roo/api" +import { ExtensionMessage } from "@roo/ExtensionMessage" + +import { vscode } from "@src/utils/vscode" +import { useExtensionState } from "@src/context/ExtensionStateContext" +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { Button } from "@src/components/ui" + +import { inputEventTransform } from "../transforms" +import { ModelPicker } from "../ModelPicker" + +type ArchGwProps = { + apiConfiguration: ProviderSettings + setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + organizationAllowList: OrganizationAllowList +} + +import { validateArchGwPreferenceConfig } from "@src/utils/validate" + +export const ArchGw = ({ apiConfiguration, setApiConfigurationField, organizationAllowList }: ArchGwProps) => { + const { t } = useAppTranslation() + + const validateArchGwPreferences = useMemo(() => { + const { archgwPreferenceConfig } = apiConfiguration + return archgwPreferenceConfig + ? validateArchGwPreferenceConfig(archgwPreferenceConfig) + : { isValid: true, errorMessage: undefined } + }, [apiConfiguration]) + + const { routerModels } = useExtensionState() + const [refreshStatus, setRefreshStatus] = useState<"idle" | "loading" | "success" | "error">("idle") + const [refreshError, setRefreshError] = useState() + const archgwErrorJustReceived = useRef(false) + + const [archgwBaseUrlSelected, setArchgwBaseUrlSelected] = useState(!!apiConfiguration?.archgwBaseUrl) + const [archgwUsePreferencesSelected, setArchgwUsePreferences] = useState(!!apiConfiguration?.archgwUsePreferences) + + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + const message = event.data + if (message.type === "singleRouterModelFetchResponse" && !message.success) { + const providerName = message.values?.provider as RouterName + if (providerName === "archgw") { + archgwErrorJustReceived.current = true + setRefreshStatus("error") + setRefreshError(message.error) + } + } else if (message.type === "routerModels") { + // If we were loading and no specific error for archgw was just received, mark as success. + // The ModelPicker will show available models or "no models found". + if (refreshStatus === "loading") { + if (!archgwErrorJustReceived.current) { + setRefreshStatus("success") + } + // If archgwErrorJustReceived.current is true, status is already (or will be) "error". + } + } + } + + window.addEventListener("message", handleMessage) + return () => { + window.removeEventListener("message", handleMessage) + } + }, [refreshStatus, refreshError, setRefreshStatus, setRefreshError]) + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ProviderSettings[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + const handleRefreshModels = useCallback(() => { + archgwErrorJustReceived.current = false // Reset flag on new refresh action + setRefreshStatus("loading") + setRefreshError(undefined) + + const url = apiConfiguration.archgwBaseUrl + + if (!url) { + setRefreshStatus("error") + setRefreshError(t("settings:providers.refreshModels.missingConfig")) + return + } + vscode.postMessage({ type: "requestRouterModels", values: { archgwBaseUrl: url } }) + }, [apiConfiguration, setRefreshStatus, setRefreshError, t]) + + return ( + <> + + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ + { + setArchgwBaseUrlSelected(checked) + + if (!checked) { + setApiConfigurationField("archgwBaseUrl", "") + } + }}> + {t("settings:providers.useCustomBaseUrl")} + + {archgwBaseUrlSelected && ( + <> + + + )} + + + {refreshStatus === "loading" && ( +
+ {t("settings:providers.refreshModels.loading")} +
+ )} + {refreshStatus === "success" && ( +
{t("settings:providers.refreshModels.success")}
+ )} + {refreshStatus === "error" && ( +
+ {refreshError || t("settings:providers.refreshModels.error")} +
+ )} + + + { + setArchgwUsePreferences(checked) + + setApiConfigurationField("archgwUsePreferences", checked) + }}> + {t("settings:providers.usePreferenceBasedRouting")} + + + {archgwUsePreferencesSelected && ( + <> +
{t("settings:providers.routingConfig")}
+ + setApiConfigurationField("archgwPreferenceConfig", (e.target as HTMLInputElement).value) + } + className="w-full font-mono text-sm" + resize="vertical" + /> + +
+ {t("settings:providers.archgwPreferenceConfigUse")} +
+
+								{`
+  - model: openai/gpt-4o
+    routing_preferences:
+      - name: code understanding
+        description: understand and explain code
+
+  - model: openai/gpt-4.1
+    routing_preferences:
+      - name: code generation
+        description: generating new code
+                `}
+							
+
+ {t("settings:providers.archgwPreferenceConfigDesc")} +
+ + {!validateArchGwPreferences.isValid ? ( +
+ {validateArchGwPreferences.errorMessage || t("settings:providers.invalidRoutingConfig")} +
+ ) : ( + validateArchGwPreferences.errorMessage && ( +
+ {validateArchGwPreferences.errorMessage} +
+ ) + )} + + )} + + ) +} diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts index 54974f7200b..60f88d0ddc6 100644 --- a/webview-ui/src/components/settings/providers/index.ts +++ b/webview-ui/src/components/settings/providers/index.ts @@ -19,3 +19,4 @@ export { Vertex } from "./Vertex" export { VSCodeLM } from "./VSCodeLM" export { XAI } from "./XAI" export { LiteLLM } from "./LiteLLM" +export { ArchGw } from "./ArchGw" diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 928ebb42f46..d276caccb0e 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -32,6 +32,7 @@ import { glamaDefaultModelId, unboundDefaultModelId, litellmDefaultModelId, + archgwDefaultModelId, claudeCodeDefaultModelId, claudeCodeModels, } from "@roo-code/types" @@ -120,6 +121,13 @@ function getSelectedModel({ const info = routerModels.litellm[id] return { id, info } } + + case "archgw": { + const id = apiConfiguration.archgwModelId ?? "gpt-4o" + const info = routerModels.archgw[id] + return info ? { id, info } : { id: archgwDefaultModelId, info: routerModels.archgw[archgwDefaultModelId] } + } + case "xai": { const id = apiConfiguration.apiModelId ?? xaiDefaultModelId const info = xaiModels[id as keyof typeof xaiModels] diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index cf17a6f919d..419b682ca3f 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "L'API del model de llenguatge de VS Code us permet executar models proporcionats per altres extensions de VS Code (incloent-hi, però no limitat a, GitHub Copilot). La manera més senzilla de començar és instal·lar les extensions Copilot i Copilot Chat des del VS Code Marketplace.", "awsCustomArnUse": "Introduïu un ARN vàlid d'Amazon Bedrock per al model que voleu utilitzar. Exemples de format:", "awsCustomArnDesc": "Assegureu-vos que la regió a l'ARN coincideix amb la regió d'AWS seleccionada anteriorment.", + "archgwPreferenceConfigUse": "Introduïu una configuració YAML vàlida per a l'encaminament basat en preferències d'Arch Gateway. La configuració ha d'incloure el nom de la ruta, l'identificador del model i les preferències d'ús. Per exemple:", + "archgwPreferenceConfigDesc": "Assegureu-vos que l'identificador del model existeixi a la configuració d'Arch Gateway.", + "invalidRoutingConfig": "Format de configuració d'encaminament no vàlid.", + "routingConfig": "Configuració d'encaminament", + "usePreferenceBasedRouting": "Utilitza l'encaminament basat en preferències", "openRouterApiKey": "Clau API d'OpenRouter", "getOpenRouterApiKey": "Obtenir clau API d'OpenRouter", "apiKeyStorageNotice": "Les claus API s'emmagatzemen de forma segura a l'Emmagatzematge Secret de VSCode", @@ -689,7 +694,13 @@ "modelAvailability": "L'ID de model ({{modelId}}) que heu proporcionat no està disponible. Si us plau, trieu un altre model.", "providerNotAllowed": "El proveïdor '{{provider}}' no està permès per la vostra organització", "modelNotAllowed": "El model '{{model}}' no està permès per al proveïdor '{{provider}}' per la vostra organització", - "profileInvalid": "Aquest perfil conté un proveïdor o model que no està permès per la vostra organització" + "profileInvalid": "Aquest perfil conté un proveïdor o model que no està permès per la vostra organització", + "routingConfig": { + "invalidSchema": "El YAML ha de ser una llista d'objectes, cadascun amb una clau 'model' i una matriu 'routing_preferences'.", + "missingModelKey": "Cada objecte de configuració d'encaminament ha de tenir una clau 'model'.", + "missingPreferencesMap": "Cada objecte de configuració d'encaminament ha de tenir una clau 'routing_preferences' amb una matriu de preferències.", + "invalidPreferences": "Cada preferència d'encaminament ha de ser un objecte amb 'name' i 'description' (ambdós de tipus cadena)." + } }, "placeholders": { "apiKey": "Introduïu la clau API...", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 20ffaec5252..45336e49f42 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "Die VS Code Language Model API ermöglicht das Ausführen von Modellen, die von anderen VS Code-Erweiterungen bereitgestellt werden (einschließlich, aber nicht beschränkt auf GitHub Copilot). Der einfachste Weg, um zu starten, besteht darin, die Erweiterungen Copilot und Copilot Chat aus dem VS Code Marketplace zu installieren.", "awsCustomArnUse": "Geben Sie eine gültige Amazon Bedrock ARN für das Modell ein, das Sie verwenden möchten. Formatbeispiele:", "awsCustomArnDesc": "Stellen Sie sicher, dass die Region in der ARN mit Ihrer oben ausgewählten AWS-Region übereinstimmt.", + "archgwPreferenceConfigUse": "Geben Sie eine gültige YAML-Konfiguration für das Arch-Gateway mit präferenzbasiertem Routing ein. Die Konfiguration sollte den Routen-Namen, die Modell-ID und die Nutzungsvorlieben enthalten. Zum Beispiel:", + "archgwPreferenceConfigDesc": "Stellen Sie sicher, dass die Modell-ID in Ihrer Arch-Gateway-Konfiguration vorhanden ist.", + "invalidRoutingConfig": "Ungültiges Format der Routing-Konfiguration.", + "routingConfig": "Routing-Konfiguration", + "usePreferenceBasedRouting": "Präferenzbasiertes Routing verwenden", "openRouterApiKey": "OpenRouter API-Schlüssel", "getOpenRouterApiKey": "OpenRouter API-Schlüssel erhalten", "apiKeyStorageNotice": "API-Schlüssel werden sicher im VSCode Secret Storage gespeichert", @@ -689,7 +694,13 @@ "modelAvailability": "Die von dir angegebene Modell-ID ({{modelId}}) ist nicht verfügbar. Bitte wähle ein anderes Modell.", "providerNotAllowed": "Anbieter '{{provider}}' ist von deiner Organisation nicht erlaubt", "modelNotAllowed": "Modell '{{model}}' ist für Anbieter '{{provider}}' von deiner Organisation nicht erlaubt", - "profileInvalid": "Dieses Profil enthält einen Anbieter oder ein Modell, das von deiner Organisation nicht erlaubt ist" + "profileInvalid": "Dieses Profil enthält einen Anbieter oder ein Modell, das von deiner Organisation nicht erlaubt ist", + "routingConfig": { + "invalidSchema": "YAML muss eine Liste von Objekten sein, die jeweils einen 'model'-Schlüssel und ein 'routing_preferences'-Array enthalten.", + "missingModelKey": "Jedes Routing-Konfigurationsobjekt muss einen 'model'-Schlüssel enthalten.", + "missingPreferencesMap": "Jedes Routing-Konfigurationsobjekt muss einen 'routing_preferences'-Schlüssel mit einem Array von Präferenzen enthalten.", + "invalidPreferences": "Jede Routing-Präferenz muss ein Objekt mit 'name' und 'description' sein (beides Zeichenfolgen)." + } }, "placeholders": { "apiKey": "API-Schlüssel eingeben...", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 4a826bddab4..8ac241556b1 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": " The VS Code Language Model API allows you to run models provided by other VS Code extensions (including but not limited to GitHub Copilot). The easiest way to get started is to install the Copilot and Copilot Chat extensions from the VS Code Marketplace.", "awsCustomArnUse": "Enter a valid Amazon Bedrock ARN for the model you want to use. Format examples:", "awsCustomArnDesc": "Make sure the region in the ARN matches your selected AWS Region above.", + "archgwPreferenceConfigUse": "Enter a valid YAML configuration for Arch Gateway preference-based routing. The configuration should include the route name, model ID and the usage preferences. For example:", + "archgwPreferenceConfigDesc": "Make sure the model ID exists in your arch gateway configuration.", + "invalidRoutingConfig": "Invalid routing config format.", + "routingConfig": "Routing Config", + "usePreferenceBasedRouting": "Use preference based routing", "openRouterApiKey": "OpenRouter API Key", "getOpenRouterApiKey": "Get OpenRouter API Key", "apiKeyStorageNotice": "API keys are stored securely in VSCode's Secret Storage", @@ -689,7 +694,13 @@ "modelAvailability": "The model ID ({{modelId}}) you provided is not available. Please choose a different model.", "providerNotAllowed": "Provider '{{provider}}' is not allowed by your organization", "modelNotAllowed": "Model '{{model}}' is not allowed for provider '{{provider}}' by your organization", - "profileInvalid": "This profile contains a provider or model that is not allowed by your organization" + "profileInvalid": "This profile contains a provider or model that is not allowed by your organization", + "routingConfig": { + "invalidSchema": "YAML must be a list of objects, each with 'model' and 'routing_preferences' array.", + "missingModelKey": "Each routing config object must have a 'model' key.", + "missingPreferencesMap": "Each routing config object must have a 'routing_preferences' key with an array of preferences.", + "invalidPreferences": "Each routing preference must be an object with 'name' and 'description' (both strings)." + } }, "placeholders": { "apiKey": "Enter API Key...", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 4c4f24bb0f9..87bcf08f094 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "La API del Modelo de Lenguaje de VS Code le permite ejecutar modelos proporcionados por otras extensiones de VS Code (incluido, entre otros, GitHub Copilot). La forma más sencilla de empezar es instalar las extensiones Copilot y Copilot Chat desde el VS Code Marketplace.", "awsCustomArnUse": "Ingrese un ARN de Amazon Bedrock válido para el modelo que desea utilizar. Ejemplos de formato:", "awsCustomArnDesc": "Asegúrese de que la región en el ARN coincida con la región de AWS seleccionada anteriormente.", + "archgwPreferenceConfigUse": "Ingrese una configuración YAML válida para el enrutamiento basado en preferencias de Arch Gateway. La configuración debe incluir el nombre de la ruta, el ID del modelo y las preferencias de uso. Por ejemplo:", + "archgwPreferenceConfigDesc": "Asegúrese de que el ID del modelo exista en su configuración de Arch Gateway.", + "invalidRoutingConfig": "Formato de configuración de enrutamiento no válido.", + "routingConfig": "Configuración de enrutamiento", + "usePreferenceBasedRouting": "Usar enrutamiento basado en preferencias", "openRouterApiKey": "Clave API de OpenRouter", "getOpenRouterApiKey": "Obtener clave API de OpenRouter", "apiKeyStorageNotice": "Las claves API se almacenan de forma segura en el Almacenamiento Secreto de VSCode", @@ -689,7 +694,13 @@ "modelAvailability": "El ID de modelo ({{modelId}}) que proporcionó no está disponible. Por favor, elija un modelo diferente.", "providerNotAllowed": "El proveedor '{{provider}}' no está permitido por su organización", "modelNotAllowed": "El modelo '{{model}}' no está permitido para el proveedor '{{provider}}' por su organización", - "profileInvalid": "Este perfil contiene un proveedor o modelo que no está permitido por su organización" + "profileInvalid": "Este perfil contiene un proveedor o modelo que no está permitido por su organización", + "routingConfig": { + "invalidSchema": "El YAML debe ser una lista de objetos, cada uno con una clave 'model' y un arreglo 'routing_preferences'.", + "missingModelKey": "Cada objeto de configuración de enrutamiento debe tener una clave 'model'.", + "missingPreferencesMap": "Cada objeto de configuración de enrutamiento debe tener una clave 'routing_preferences' con un arreglo de preferencias.", + "invalidPreferences": "Cada preferencia de enrutamiento debe ser un objeto con 'name' y 'description' (ambos como cadenas de texto)." + } }, "placeholders": { "apiKey": "Ingrese clave API...", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 6e7a964694b..d8e7195cd45 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "L'API du modèle de langage VS Code vous permet d'exécuter des modèles fournis par d'autres extensions VS Code (y compris, mais sans s'y limiter, GitHub Copilot). Le moyen le plus simple de commencer est d'installer les extensions Copilot et Copilot Chat depuis le VS Code Marketplace.", "awsCustomArnUse": "Entrez un ARN Amazon Bedrock valide pour le modèle que vous souhaitez utiliser. Exemples de format :", "awsCustomArnDesc": "Assurez-vous que la région dans l'ARN correspond à la région AWS sélectionnée ci-dessus.", + "archgwPreferenceConfigUse": "Entrez une configuration YAML valide pour le routage basé sur les préférences d'Arch Gateway. La configuration doit inclure le nom de la route, l'ID du modèle et les préférences d'utilisation. Par exemple:", + "archgwPreferenceConfigDesc": "Assurez-vous que l'ID du modèle existe dans votre configuration d'Arch Gateway.", + "invalidRoutingConfig": "Format de configuration de routage invalide.", + "routingConfig": "Configuration de routage", + "usePreferenceBasedRouting": "Utiliser le routage basé sur les préférences", "openRouterApiKey": "Clé API OpenRouter", "getOpenRouterApiKey": "Obtenir la clé API OpenRouter", "apiKeyStorageNotice": "Les clés API sont stockées en toute sécurité dans le stockage sécurisé de VSCode", @@ -689,7 +694,13 @@ "modelAvailability": "L'ID de modèle ({{modelId}}) que vous avez fourni n'est pas disponible. Veuillez choisir un modèle différent.", "providerNotAllowed": "Le fournisseur '{{provider}}' n'est pas autorisé par votre organisation", "modelNotAllowed": "Le modèle '{{model}}' n'est pas autorisé pour le fournisseur '{{provider}}' par votre organisation", - "profileInvalid": "Ce profil contient un fournisseur ou un modèle qui n'est pas autorisé par votre organisation" + "profileInvalid": "Ce profil contient un fournisseur ou un modèle qui n'est pas autorisé par votre organisation", + "routingConfig": { + "invalidSchema": "Le YAML doit être une liste d'objets, chacun avec une clé 'model' et un tableau 'routing_preferences'.", + "missingModelKey": "Chaque objet de configuration de routage doit avoir une clé 'model'.", + "missingPreferencesMap": "Chaque objet de configuration de routage doit avoir une clé 'routing_preferences' avec un tableau de préférences.", + "invalidPreferences": "Chaque préférence de routage doit être un objet avec 'name' et 'description' (toutes deux des chaînes de caractères)." + } }, "placeholders": { "apiKey": "Saisissez la clé API...", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index ca255ca44ee..a740c7a15f2 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "VS कोड भाषा मॉडल API आपको अन्य VS कोड एक्सटेंशन (जैसे GitHub Copilot) द्वारा प्रदान किए गए मॉडल चलाने की अनुमति देता है। शुरू करने का सबसे आसान तरीका VS कोड मार्केटप्लेस से Copilot और Copilot चैट एक्सटेंशन इंस्टॉल करना है।", "awsCustomArnUse": "आप जिस मॉडल का उपयोग करना चाहते हैं, उसके लिए एक वैध AWS बेडरॉक ARN दर्ज करें। प्रारूप उदाहरण:", "awsCustomArnDesc": "सुनिश्चित करें कि ARN में क्षेत्र ऊपर चयनित AWS क्षेत्र से मेल खाता है।", + "archgwPreferenceConfigUse": "Arch Gateway प्राथमिकता-आधारित रूटिंग के लिए एक मान्य YAML कॉन्फ़िगरेशन दर्ज करें। कॉन्फ़िगरेशन में रूट नाम, मॉडल आईडी और उपयोग प्राथमिकताएँ शामिल होनी चाहिए। उदाहरण के लिए:", + "archgwPreferenceConfigDesc": "सुनिश्चित करें कि मॉडल आईडी आपकी आर्क गेटवे कॉन्फ़िगरेशन में मौजूद है।", + "invalidRoutingConfig": "अमान्य रूटिंग कॉन्फ़िग प्रारूप।", + "routingConfig": "रूटिंग कॉन्फ़िग", + "usePreferenceBasedRouting": "प्राथमिकता आधारित रूटिंग का उपयोग करें", "openRouterApiKey": "OpenRouter API कुंजी", "getOpenRouterApiKey": "OpenRouter API कुंजी प्राप्त करें", "apiKeyStorageNotice": "API कुंजियाँ VSCode के सुरक्षित स्टोरेज में सुरक्षित रूप से संग्रहीत हैं", @@ -689,7 +694,13 @@ "modelAvailability": "आपके द्वारा प्रदान की गई मॉडल ID ({{modelId}}) उपलब्ध नहीं है। कृपया कोई अन्य मॉडल चुनें।", "providerNotAllowed": "प्रदाता '{{provider}}' आपके संगठन द्वारा अनुमत नहीं है", "modelNotAllowed": "मॉडल '{{model}}' प्रदाता '{{provider}}' के लिए आपके संगठन द्वारा अनुमत नहीं है", - "profileInvalid": "इस प्रोफ़ाइल में एक प्रदाता या मॉडल शामिल है जो आपके संगठन द्वारा अनुमत नहीं है" + "profileInvalid": "इस प्रोफ़ाइल में एक प्रदाता या मॉडल शामिल है जो आपके संगठन द्वारा अनुमत नहीं है", + "routingConfig": { + "invalidSchema": "YAML को वस्तुओं की एक सूची होना चाहिए, प्रत्येक में 'model' और 'routing_preferences' सरणी होनी चाहिए।", + "missingModelKey": "प्रत्येक रूटिंग कॉन्फ़िग ऑब्जेक्ट में 'model' कुंजी होनी चाहिए।", + "missingPreferencesMap": "प्रत्येक रूटिंग कॉन्फ़िग ऑब्जेक्ट में 'routing_preferences' कुंजी होनी चाहिए जिसमें प्राथमिकताओं की एक सरणी हो।", + "invalidPreferences": "प्रत्येक रूटिंग प्राथमिकता को 'name' और 'description' (दोनों स्ट्रिंग) के साथ एक ऑब्जेक्ट होना चाहिए।" + } }, "placeholders": { "apiKey": "API कुंजी दर्ज करें...", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 0bff5ef4a17..afcc0919605 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -227,6 +227,11 @@ "vscodeLmDescription": " API Model Bahasa VS Code memungkinkan kamu menjalankan model yang disediakan oleh ekstensi VS Code lainnya (termasuk namun tidak terbatas pada GitHub Copilot). Cara termudah untuk memulai adalah menginstal ekstensi Copilot dan Copilot Chat dari VS Code Marketplace.", "awsCustomArnUse": "Masukkan ARN Amazon Bedrock yang valid untuk model yang ingin kamu gunakan. Contoh format:", "awsCustomArnDesc": "Pastikan region di ARN cocok dengan AWS Region yang kamu pilih di atas.", + "archgwPreferenceConfigUse": "Masukkan konfigurasi YAML yang valid untuk routing berbasis preferensi di Arch Gateway. Konfigurasi harus mencakup nama rute, ID model, dan preferensi penggunaan. Contohnya:", + "archgwPreferenceConfigDesc": "Pastikan ID model ada dalam konfigurasi Arch Gateway Anda.", + "invalidRoutingConfig": "Format konfigurasi routing tidak valid.", + "routingConfig": "Konfigurasi Routing", + "usePreferenceBasedRouting": "Gunakan routing berbasis preferensi", "openRouterApiKey": "OpenRouter API Key", "getOpenRouterApiKey": "Dapatkan OpenRouter API Key", "apiKeyStorageNotice": "API key disimpan dengan aman di Secret Storage VSCode", @@ -718,7 +723,13 @@ "modelAvailability": "Model ID ({{modelId}}) yang kamu berikan tidak tersedia. Silakan pilih model yang berbeda.", "providerNotAllowed": "Provider '{{provider}}' tidak diizinkan oleh organisasi kamu", "modelNotAllowed": "Model '{{model}}' tidak diizinkan untuk provider '{{provider}}' oleh organisasi kamu", - "profileInvalid": "Profil ini berisi provider atau model yang tidak diizinkan oleh organisasi kamu" + "profileInvalid": "Profil ini berisi provider atau model yang tidak diizinkan oleh organisasi kamu", + "routingConfig": { + "invalidSchema": "YAML harus berupa daftar objek, masing-masing dengan kunci 'model' dan array 'routing_preferences'.", + "missingModelKey": "Setiap objek konfigurasi routing harus memiliki kunci 'model'.", + "missingPreferencesMap": "Setiap objek konfigurasi routing harus memiliki kunci 'routing_preferences' dengan array preferensi.", + "invalidPreferences": "Setiap preferensi routing harus berupa objek dengan 'name' dan 'description' (keduanya berupa string)." + } }, "placeholders": { "apiKey": "Masukkan API Key...", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index e5cd37a1106..0eb9a6cfbeb 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "L'API del Modello di Linguaggio di VS Code consente di eseguire modelli forniti da altre estensioni di VS Code (incluso, ma non limitato a, GitHub Copilot). Il modo più semplice per iniziare è installare le estensioni Copilot e Copilot Chat dal VS Code Marketplace.", "awsCustomArnUse": "Inserisci un ARN Amazon Bedrock valido per il modello che desideri utilizzare. Esempi di formato:", "awsCustomArnDesc": "Assicurati che la regione nell'ARN corrisponda alla regione AWS selezionata sopra.", + "archgwPreferenceConfigUse": "Inserisci una configurazione YAML valida per il routing basato sulle preferenze di Arch Gateway. La configurazione deve includere il nome della rotta, l'ID del modello e le preferenze di utilizzo. Ad esempio:", + "archgwPreferenceConfigDesc": "Assicurati che l'ID del modello esista nella tua configurazione di Arch Gateway.", + "invalidRoutingConfig": "Formato di configurazione del routing non valido.", + "routingConfig": "Configurazione del routing", + "usePreferenceBasedRouting": "Usa il routing basato sulle preferenze", "openRouterApiKey": "Chiave API OpenRouter", "getOpenRouterApiKey": "Ottieni chiave API OpenRouter", "apiKeyStorageNotice": "Le chiavi API sono memorizzate in modo sicuro nell'Archivio Segreto di VSCode", @@ -689,7 +694,13 @@ "modelAvailability": "L'ID modello ({{modelId}}) fornito non è disponibile. Seleziona un modello diverso.", "providerNotAllowed": "Il fornitore '{{provider}}' non è consentito dalla tua organizzazione", "modelNotAllowed": "Il modello '{{model}}' non è consentito per il fornitore '{{provider}}' dalla tua organizzazione.", - "profileInvalid": "Questo profilo contiene un fornitore o un modello non consentito dalla tua organizzazione." + "profileInvalid": "Questo profilo contiene un fornitore o un modello non consentito dalla tua organizzazione.", + "routingConfig": { + "invalidSchema": "YAML deve essere un elenco di oggetti, ognuno con 'model' e array 'routing_preferences'.", + "missingModelKey": "Ogni oggetto di configurazione del routing deve avere una chiave 'model'.", + "missingPreferencesMap": "Ogni oggetto di configurazione del routing deve avere una chiave 'routing_preferences' con un array di preferenze.", + "invalidPreferences": "Ogni preferenza di routing deve essere un oggetto con 'name' e 'description' (entrambi stringhe)." + } }, "placeholders": { "apiKey": "Inserisci chiave API...", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index e32fac776a9..050b6fdadea 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "VS Code言語モデルAPIを使用すると、他のVS Code拡張機能(GitHub Copilotなど)が提供するモデルを実行できます。最も簡単な方法は、VS Code MarketplaceからCopilotおよびCopilot Chat拡張機能をインストールすることです。", "awsCustomArnUse": "使用したいモデルの有効なAmazon Bedrock ARNを入力してください。形式の例:", "awsCustomArnDesc": "ARN内のリージョンが上で選択したAWSリージョンと一致していることを確認してください。", + "archgwPreferenceConfigUse": "Arch Gatewayの優先度ベースのルーティングに対する有効なYAML構成を入力してください。構成には、ルート名、モデルID、および使用の優先度が含まれている必要があります。例えば:", + "archgwPreferenceConfigDesc": "モデルIDがアーチゲートウェイの構成に存在することを確認してください。", + "invalidRoutingConfig": "無効なルーティング設定フォーマットです。", + "routingConfig": "ルーティング設定", + "usePreferenceBasedRouting": "優先度ベースのルーティングを使用", "openRouterApiKey": "OpenRouter APIキー", "getOpenRouterApiKey": "OpenRouter APIキーを取得", "apiKeyStorageNotice": "APIキーはVSCodeのシークレットストレージに安全に保存されます", @@ -689,7 +694,13 @@ "modelAvailability": "指定されたモデルID({{modelId}})は利用できません。別のモデルを選択してください。", "providerNotAllowed": "プロバイダー「{{provider}}」は組織によって許可されていません", "modelNotAllowed": "モデル「{{model}}」はプロバイダー「{{provider}}」に対して組織によって許可されていません", - "profileInvalid": "このプロファイルには、組織によって許可されていないプロバイダーまたはモデルが含まれています" + "profileInvalid": "このプロファイルには、組織によって許可されていないプロバイダーまたはモデルが含まれています", + "routingConfig": { + "invalidSchema": "YAMLは、各オブジェクトに'model'と'routing_preferences'配列を持つリストである必要があります。", + "missingModelKey": "各ルーティング構成オブジェクトには'model'キーが必要です。", + "missingPreferencesMap": "各ルーティング構成オブジェクトには、優先度の配列を持つ'routing_preferences'キーが必要です。", + "invalidPreferences": "各ルーティング優先度は'name'と'description'(両方とも文字列)を持つオブジェクトである必要があります。" + } }, "placeholders": { "apiKey": "API キーを入力...", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index f48860b0aeb..b2f366b105e 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "VS Code 언어 모델 API를 사용하면 GitHub Copilot을 포함한 기타 VS Code 확장 프로그램이 제공하는 모델을 실행할 수 있습니다. 시작하려면 VS Code 마켓플레이스에서 Copilot 및 Copilot Chat 확장 프로그램을 설치하는 것이 가장 쉽습니다.", "awsCustomArnUse": "사용하려는 모델의 유효한 Amazon Bedrock ARN을 입력하세요. 형식 예시:", "awsCustomArnDesc": "ARN의 리전이 위에서 선택한 AWS 리전과 일치하는지 확인하세요.", + "archgwPreferenceConfigUse": "Arch Gateway 기본 라우팅을 위한 유효한 YAML 구성을 입력하세요. 구성에는 경로 이름, 모델 ID 및 사용 기본 설정이 포함되어야 합니다. 예:", + "archgwPreferenceConfigDesc": "모델 ID가 아치 게이트웨이 구성에 존재하는지 확인하세요.", + "invalidRoutingConfig": "잘못된 라우팅 구성 형식입니다.", + "routingConfig": "라우팅 구성", + "usePreferenceBasedRouting": "기본 설정 기반 라우팅 사용", "openRouterApiKey": "OpenRouter API 키", "getOpenRouterApiKey": "OpenRouter API 키 받기", "apiKeyStorageNotice": "API 키는 VSCode의 보안 저장소에 안전하게 저장됩니다", @@ -689,7 +694,13 @@ "modelAvailability": "제공한 모델 ID({{modelId}})를 사용할 수 없습니다. 다른 모델을 선택하세요.", "providerNotAllowed": "제공자 '{{provider}}'는 조직에서 허용되지 않습니다", "modelNotAllowed": "모델 '{{model}}'은 제공자 '{{provider}}'에 대해 조직에서 허용되지 않습니다", - "profileInvalid": "이 프로필에는 조직에서 허용되지 않는 제공자 또는 모델이 포함되어 있습니다" + "profileInvalid": "이 프로필에는 조직에서 허용되지 않는 제공자 또는 모델이 포함되어 있습니다", + "routingConfig": { + "invalidSchema": "YAML은 각 객체에 'model' 및 'routing_preferences' 배열이 있는 목록이어야 합니다.", + "missingModelKey": "각 라우팅 구성 객체에는 'model' 키가 필요합니다.", + "missingPreferencesMap": "각 라우팅 구성 객체에는 우선 순위 배열이 있는 'routing_preferences' 키가 필요합니다.", + "invalidPreferences": "각 라우팅 우선 순위는 'name' 및 'description'(둘 다 문자열)을 가진 객체여야 합니다." + } }, "placeholders": { "apiKey": "API 키 입력...", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index bf6e65c995d..c806779f7ab 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "De VS Code Language Model API stelt je in staat modellen te draaien die door andere VS Code-extensies worden geleverd (waaronder GitHub Copilot). De eenvoudigste manier om te beginnen is door de Copilot- en Copilot Chat-extensies te installeren vanuit de VS Code Marketplace.", "awsCustomArnUse": "Voer een geldige Amazon Bedrock ARN in voor het model dat je wilt gebruiken. Voorbeeldformaten:", "awsCustomArnDesc": "Zorg ervoor dat de regio in de ARN overeenkomt met je geselecteerde AWS-regio hierboven.", + "archgwPreferenceConfigUse": "Voer een geldige YAML-configuratie in voor Arch Gateway voorkeur-gebaseerde routering. De configuratie moet de routenaam, model-ID en de gebruiksvoorkeuren bevatten. Bijvoorbeeld:", + "archgwPreferenceConfigDesc": "Zorg ervoor dat de model-ID bestaat in je arch gateway-configuratie.", + "invalidRoutingConfig": "Ongeldig routeringsconfiguratieformaat.", + "routingConfig": "Routeringsconfiguratie", + "usePreferenceBasedRouting": "Gebruik voorkeur-gebaseerde routering", "openRouterApiKey": "OpenRouter API-sleutel", "getOpenRouterApiKey": "OpenRouter API-sleutel ophalen", "apiKeyStorageNotice": "API-sleutels worden veilig opgeslagen in de geheime opslag van VSCode", @@ -689,7 +694,13 @@ "modelAvailability": "Het opgegeven model-ID ({{modelId}}) is niet beschikbaar. Kies een ander model.", "providerNotAllowed": "Provider '{{provider}}' is niet toegestaan door je organisatie", "modelNotAllowed": "Model '{{model}}' is niet toegestaan voor provider '{{provider}}' door je organisatie", - "profileInvalid": "Dit profiel bevat een provider of model dat niet is toegestaan door je organisatie" + "profileInvalid": "Dit profiel bevat een provider of model dat niet is toegestaan door je organisatie", + "routingConfig": { + "invalidSchema": "YAML moet een lijst zijn van objecten, elk met een 'model' en 'routing_preferences' array.", + "missingModelKey": "Elk routingconfiguratie-object moet een 'model' sleutel hebben.", + "missingPreferencesMap": "Elk routingconfiguratie-object moet een 'routing_preferences' sleutel hebben met een array van voorkeuren.", + "invalidPreferences": "Elke routingvoorkeur moet een object zijn met 'name' en 'description' (beide strings)." + } }, "placeholders": { "apiKey": "Voer API-sleutel in...", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index d42e4f51a99..83ccb4a6692 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "Interfejs API modelu językowego VS Code umożliwia uruchamianie modeli dostarczanych przez inne rozszerzenia VS Code (w tym, ale nie tylko, GitHub Copilot). Najłatwiejszym sposobem na rozpoczęcie jest zainstalowanie rozszerzeń Copilot i Copilot Chat z VS Code Marketplace.", "awsCustomArnUse": "Wprowadź prawidłowy Amazon Bedrock ARN dla modelu, którego chcesz użyć. Przykłady formatu:", "awsCustomArnDesc": "Upewnij się, że region w ARN odpowiada wybranemu powyżej regionowi AWS.", + "archgwPreferenceConfigUse": "Wprowadź prawidłową konfigurację YAML dla routingu opartego na preferencjach Arch Gateway. Konfiguracja powinna zawierać nazwę trasy, identyfikator modelu i preferencje użytkowania. Na przykład:", + "archgwPreferenceConfigDesc": "Upewnij się, że identyfikator modelu istnieje w konfiguracji bramy arch.", + "invalidRoutingConfig": "Nieprawidłowy format konfiguracji routingu.", + "routingConfig": "Konfiguracja routingu", + "usePreferenceBasedRouting": "Użyj routingu opartego na preferencjach", "openRouterApiKey": "Klucz API OpenRouter", "getOpenRouterApiKey": "Uzyskaj klucz API OpenRouter", "apiKeyStorageNotice": "Klucze API są bezpiecznie przechowywane w Tajnym Magazynie VSCode", @@ -689,7 +694,13 @@ "modelAvailability": "Podane ID modelu ({{modelId}}) jest niedostępne. Wybierz inny model.", "providerNotAllowed": "Dostawca '{{provider}}' nie jest dozwolony przez Twoją organizację", "modelNotAllowed": "Model '{{model}}' nie jest dozwolony dla dostawcy '{{provider}}' przez Twoją organizację", - "profileInvalid": "Ten profil zawiera dostawcę lub model, który nie jest dozwolony przez Twoją organizację" + "profileInvalid": "Ten profil zawiera dostawcę lub model, który nie jest dozwolony przez Twoją organizację", + "routingConfig": { + "invalidSchema": "YAML musi być listą obiektów, z których każdy ma tablicę 'model' i 'routing_preferences'.", + "missingModelKey": "Każdy obiekt konfiguracji routingu musi mieć klucz 'model'.", + "missingPreferencesMap": "Każdy obiekt konfiguracji routingu musi mieć klucz 'routing_preferences' z tablicą preferencji.", + "invalidPreferences": "Każda preferencja routingu musi być obiektem z 'name' i 'description' (oba ciągi znaków)." + } }, "placeholders": { "apiKey": "Wprowadź klucz API...", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 924542cf096..0af35b15cc4 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "A API do Modelo de Linguagem do VS Code permite executar modelos fornecidos por outras extensões do VS Code (incluindo, mas não se limitando, ao GitHub Copilot). A maneira mais fácil de começar é instalar as extensões Copilot e Copilot Chat no VS Code Marketplace.", "awsCustomArnUse": "Insira um ARN Amazon Bedrock válido para o modelo que deseja usar. Exemplos de formato:", "awsCustomArnDesc": "Certifique-se de que a região no ARN corresponde à região AWS selecionada acima.", + "archgwPreferenceConfigUse": "Insira uma configuração YAML válida para o roteamento baseado em preferências do Arch Gateway. A configuração deve incluir o nome da rota, ID do modelo e as preferências de uso. Por exemplo:", + "archgwPreferenceConfigDesc": "Certifique-se de que o ID do modelo exista na sua configuração do Arch Gateway.", + "invalidRoutingConfig": "Formato de configuração de roteamento inválido.", + "routingConfig": "Configuração de Roteamento", + "usePreferenceBasedRouting": "Usar roteamento baseado em preferências", "openRouterApiKey": "Chave de API OpenRouter", "getOpenRouterApiKey": "Obter chave de API OpenRouter", "apiKeyStorageNotice": "As chaves de API são armazenadas com segurança no Armazenamento Secreto do VSCode", @@ -689,7 +694,13 @@ "modelAvailability": "O ID do modelo ({{modelId}}) que você forneceu não está disponível. Por favor, escolha outro modelo.", "providerNotAllowed": "O provedor '{{provider}}' não é permitido pela sua organização", "modelNotAllowed": "O modelo '{{model}}' não é permitido para o provedor '{{provider}}' pela sua organização", - "profileInvalid": "Este perfil contém um provedor ou modelo que não é permitido pela sua organização" + "profileInvalid": "Este perfil contém um provedor ou modelo que não é permitido pela sua organização", + "routingConfig": { + "invalidSchema": "YAML deve ser uma lista de objetos, cada um com 'model' e 'routing_preferences' array.", + "missingModelKey": "Cada objeto de configuração de roteamento deve ter uma chave 'model'.", + "missingPreferencesMap": "Cada objeto de configuração de roteamento deve ter uma chave 'routing_preferences' com um array de preferências.", + "invalidPreferences": "Cada preferência de roteamento deve ser um objeto com 'name' e 'description' (ambos strings)." + } }, "placeholders": { "apiKey": "Digite a chave API...", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index cf719b09769..3d430b6df8c 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "API языковой модели VS Code позволяет запускать модели, предоставляемые другими расширениями VS Code (включая, но не ограничиваясь GitHub Copilot). Для начала установите расширения Copilot и Copilot Chat из VS Code Marketplace.", "awsCustomArnUse": "Введите действительный Amazon Bedrock ARN для используемой модели. Примеры формата:", "awsCustomArnDesc": "Убедитесь, что регион в ARN совпадает с выбранным выше регионом AWS.", + "archgwPreferenceConfigUse": "Введите действительную YAML-конфигурацию для маршрутизации на основе предпочтений Arch Gateway. Конфигурация должна включать имя маршрута, ID модели и предпочтения использования. Например:", + "archgwPreferenceConfigDesc": "Убедитесь, что ID модели существует в вашей конфигурации Arch Gateway.", + "invalidRoutingConfig": "Неверный формат конфигурации маршрутизации.", + "routingConfig": "Конфигурация маршрутизации", + "usePreferenceBasedRouting": "Использовать маршрутизацию на основе предпочтений", "openRouterApiKey": "OpenRouter API-ключ", "getOpenRouterApiKey": "Получить OpenRouter API-ключ", "apiKeyStorageNotice": "API-ключи хранятся безопасно в Secret Storage VSCode", @@ -689,7 +694,13 @@ "modelAvailability": "ID модели ({{modelId}}), который вы указали, недоступен. Пожалуйста, выберите другую модель.", "providerNotAllowed": "Провайдер '{{provider}}' не разрешен вашей организацией", "modelNotAllowed": "Модель '{{model}}' не разрешена для провайдера '{{provider}}' вашей организацией", - "profileInvalid": "Этот профиль содержит провайдера или модель, которые не разрешены вашей организацией" + "profileInvalid": "Этот профиль содержит провайдера или модель, которые не разрешены вашей организацией", + "routingConfig": { + "invalidSchema": "YAML должен быть списком объектов, каждый из которых имеет массив 'model' и 'routing_preferences'.", + "missingModelKey": "Каждый объект конфигурации маршрутизации должен иметь ключ 'model'.", + "missingPreferencesMap": "Каждый объект конфигурации маршрутизации должен иметь ключ 'routing_preferences' с массивом предпочтений.", + "invalidPreferences": "Каждое предпочтение маршрутизации должно быть объектом с 'name' и 'description' (оба строки)." + } }, "placeholders": { "apiKey": "Введите API-ключ...", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index fc8fc9c6775..71a3d8009c7 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "VS Code Dil Modeli API'si, diğer VS Code uzantıları tarafından sağlanan modelleri çalıştırmanıza olanak tanır (GitHub Copilot dahil ancak bunlarla sınırlı değildir). Başlamanın en kolay yolu, VS Code Marketplace'ten Copilot ve Copilot Chat uzantılarını yüklemektir.", "awsCustomArnUse": "Kullanmak istediğiniz model için geçerli bir Amazon Bedrock ARN'si girin. Format örnekleri:", "awsCustomArnDesc": "ARN içindeki bölgenin yukarıda seçilen AWS Bölgesiyle eşleştiğinden emin olun.", + "archgwPreferenceConfigUse": "Arch Gateway tercih tabanlı yönlendirme için geçerli bir YAML yapılandırması girin. Yapılandırma, rota adını, model kimliğini ve kullanım tercihlerini içermelidir. Örneğin:", + "archgwPreferenceConfigDesc": "Model kimliğinin arch gateway yapılandırmanızda mevcut olduğundan emin olun.", + "invalidRoutingConfig": "Geçersiz yönlendirme yapılandırma formatı.", + "routingConfig": "Yönlendirme Yapılandırması", + "usePreferenceBasedRouting": "Tercih tabanlı yönlendirmeyi kullan", "openRouterApiKey": "OpenRouter API Anahtarı", "getOpenRouterApiKey": "OpenRouter API Anahtarı Al", "apiKeyStorageNotice": "API anahtarları VSCode'un Gizli Depolamasında güvenli bir şekilde saklanır", @@ -689,7 +694,13 @@ "modelAvailability": "Sağladığınız model kimliği ({{modelId}}) kullanılamıyor. Lütfen başka bir model seçin.", "providerNotAllowed": "Sağlayıcı '{{provider}}' kuruluşunuz tarafından izin verilmiyor", "modelNotAllowed": "Model '{{model}}' sağlayıcı '{{provider}}' için kuruluşunuz tarafından izin verilmiyor", - "profileInvalid": "Bu profil, kuruluşunuz tarafından izin verilmeyen bir sağlayıcı veya model içeriyor" + "profileInvalid": "Bu profil, kuruluşunuz tarafından izin verilmeyen bir sağlayıcı veya model içeriyor", + "routingConfig": { + "invalidSchema": "YAML, her biri 'model' ve 'routing_preferences' dizisine sahip nesnelerin bir listesi olmalıdır.", + "missingModelKey": "Her yönlendirme yapılandırma nesnesinin bir 'model' anahtarı olmalıdır.", + "missingPreferencesMap": "Her yönlendirme yapılandırma nesnesinin bir dizi tercih ile 'routing_preferences' anahtarı olmalıdır.", + "invalidPreferences": "Her yönlendirme tercihi, 'name' ve 'description' (her ikisi de dize) ile bir nesne olmalıdır." + } }, "placeholders": { "apiKey": "API anahtarını girin...", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 7d3e2803ad7..cc3c2025d21 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "API Mô hình Ngôn ngữ VS Code cho phép bạn chạy các mô hình được cung cấp bởi các tiện ích mở rộng khác của VS Code (bao gồm nhưng không giới hạn ở GitHub Copilot). Cách dễ nhất để bắt đầu là cài đặt các tiện ích mở rộng Copilot và Copilot Chat từ VS Code Marketplace.", "awsCustomArnUse": "Nhập một ARN Amazon Bedrock hợp lệ cho mô hình bạn muốn sử dụng. Ví dụ về định dạng:", "awsCustomArnDesc": "Đảm bảo rằng vùng trong ARN khớp với vùng AWS đã chọn ở trên.", + "archgwPreferenceConfigUse": "Nhập một cấu hình YAML hợp lệ cho định tuyến dựa trên sở thích của Arch Gateway. Cấu hình nên bao gồm tên lộ trình, ID mô hình và các sở thích sử dụng. Ví dụ:", + "archgwPreferenceConfigDesc": "Đảm bảo rằng ID mô hình tồn tại trong cấu hình cổng arch của bạn.", + "invalidRoutingConfig": "Geçersiz yönlendirme yapılandırma formatı.", + "routingConfig": "Yönlendirme Yapılandırması", + "usePreferenceBasedRouting": "Tercih tabanlı yönlendirmeyi kullan", "openRouterApiKey": "Khóa API OpenRouter", "getOpenRouterApiKey": "Lấy khóa API OpenRouter", "apiKeyStorageNotice": "Khóa API được lưu trữ an toàn trong Bộ lưu trữ bí mật của VSCode", @@ -689,7 +694,13 @@ "modelAvailability": "ID mô hình ({{modelId}}) bạn đã cung cấp không khả dụng. Vui lòng chọn một mô hình khác.", "providerNotAllowed": "Nhà cung cấp '{{provider}}' không được phép bởi tổ chức của bạn", "modelNotAllowed": "Mô hình '{{model}}' không được phép cho nhà cung cấp '{{provider}}' bởi tổ chức của bạn", - "profileInvalid": "Hồ sơ này chứa một nhà cung cấp hoặc mô hình không được phép bởi tổ chức của bạn" + "profileInvalid": "Hồ sơ này chứa một nhà cung cấp hoặc mô hình không được phép bởi tổ chức của bạn", + "routingConfig": { + "invalidSchema": "YAML phải là một danh sách các đối tượng, mỗi đối tượng có mảng 'model' và 'routing_preferences'.", + "missingModelKey": "Mỗi đối tượng cấu hình định tuyến phải có một khóa 'model'.", + "missingPreferencesMap": "Mỗi đối tượng cấu hình định tuyến phải có một khóa 'routing_preferences' với một mảng các sở thích.", + "invalidPreferences": "Mỗi sở thích định tuyến phải là một đối tượng với 'name' và 'description' (cả hai đều là chuỗi)." + } }, "placeholders": { "apiKey": "Nhập khóa API...", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index eae71ac706f..aef0cf04b04 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "VS Code 语言模型 API 允许您运行由其他 VS Code 扩展(包括但不限于 GitHub Copilot)提供的模型。最简单的方法是从 VS Code 市场安装 Copilot 和 Copilot Chat 扩展。", "awsCustomArnUse": "请输入有效的 Amazon Bedrock ARN(Amazon资源名称),格式示例:", "awsCustomArnDesc": "请确保ARN中的区域与上方选择的AWS区域一致。", + "archgwPreferenceConfigUse": "输入有效的 YAML 配置,用于 Arch Gateway 基于偏好的路由。配置应包括路由名称、模型 ID 和使用偏好。例如:", + "archgwPreferenceConfigDesc": "确保模型 ID 存在于您的 arch gateway 配置中。", + "invalidRoutingConfig": "无效的路由配置格式。", + "routingConfig": "路由配置", + "usePreferenceBasedRouting": "使用基于偏好的路由", "openRouterApiKey": "OpenRouter API 密钥", "getOpenRouterApiKey": "获取 OpenRouter API 密钥", "apiKeyStorageNotice": "API 密钥安全存储在 VSCode 的密钥存储中", @@ -689,7 +694,13 @@ "modelAvailability": "模型ID {{modelId}} 不可用,请重新选择", "providerNotAllowed": "提供商 '{{provider}}' 不允许用于您的组织", "modelNotAllowed": "模型 '{{model}}' 不允许用于提供商 '{{provider}}',您的组织不允许", - "profileInvalid": "此配置文件包含您的组织不允许的提供商或模型" + "profileInvalid": "此配置文件包含您的组织不允许的提供商或模型", + "routingConfig": { + "invalidSchema": "YAML 必须是一个对象列表,每个对象都有 'model' 和 'routing_preferences' 数组。", + "missingModelKey": "每个路由配置对象必须有一个 'model' 键。", + "missingPreferencesMap": "每个路由配置对象必须有一个 'routing_preferences' 键,值为偏好数组。", + "invalidPreferences": "每个路由偏好必须是一个包含 'name' 和 'description'(均为字符串)的对象。" + } }, "placeholders": { "apiKey": "请输入 API 密钥...", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 420ece916e6..18c97d6d21c 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -223,6 +223,11 @@ "vscodeLmDescription": "VS Code 語言模型 API 可以讓您使用其他擴充功能(如 GitHub Copilot)提供的模型。最簡單的方式是從 VS Code Marketplace 安裝 Copilot 和 Copilot Chat 擴充套件。", "awsCustomArnUse": "輸入您要使用的模型的有效 Amazon Bedrock ARN。格式範例:", "awsCustomArnDesc": "確保 ARN 中的區域與您上面選擇的 AWS 區域相符。", + "archgwPreferenceConfigUse": "輸入有效的 YAML 配置,用於 Arch Gateway 基於偏好的路由。配置應包括路由名稱、模型 ID 和使用偏好。例如:", + "archgwPreferenceConfigDesc": "確保模型 ID 存在於您的 arch gateway 配置中。", + "invalidRoutingConfig": "無效的路由配置格式。", + "routingConfig": "路由配置", + "usePreferenceBasedRouting": "使用基于偏好的路由", "openRouterApiKey": "OpenRouter API 金鑰", "getOpenRouterApiKey": "取得 OpenRouter API 金鑰", "apiKeyStorageNotice": "API 金鑰安全儲存於 VSCode 金鑰儲存中", @@ -689,7 +694,13 @@ "modelAvailability": "您指定的模型 ID ({{modelId}}) 目前無法使用,請選擇其他模型。", "providerNotAllowed": "供應商 '{{provider}}' 不允許用於您的組織。", "modelNotAllowed": "模型 '{{model}}' 不允許用於供應商 '{{provider}}',您的組織不允許", - "profileInvalid": "此設定檔包含您的組織不允許的供應商或模型" + "profileInvalid": "此設定檔包含您的組織不允許的供應商或模型", + "routingConfig": { + "invalidSchema": "YAML 必須是一個物件列表,每個物件都有 'model' 和 'routing_preferences' 陣列。", + "missingModelKey": "每個路由配置物件必須有一個 'model' 鍵。", + "missingPreferencesMap": "每個路由配置物件必須有一個 'routing_preferences' 鍵,值為偏好陣列。", + "invalidPreferences": "每個路由偏好必須是包含 'name' 和 'description'(均為字串)的物件。" + } }, "placeholders": { "apiKey": "請輸入 API 金鑰...", diff --git a/webview-ui/src/utils/__tests__/validate.test.ts b/webview-ui/src/utils/__tests__/validate.test.ts index 3a60c27f8ad..786831102c1 100644 --- a/webview-ui/src/utils/__tests__/validate.test.ts +++ b/webview-ui/src/utils/__tests__/validate.test.ts @@ -38,6 +38,7 @@ describe("Model Validation Functions", () => { litellm: {}, ollama: {}, lmstudio: {}, + archgw: {}, } const allowAllOrganization: OrganizationAllowList = { diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index 1040b60c2df..3ab9ab14fa5 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -4,6 +4,8 @@ import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types" import { isRouterName, RouterModels } from "@roo/api" +import * as yaml from "yaml" + export function validateApiConfiguration( apiConfiguration: ProviderSettings, routerModels?: RouterModels, @@ -236,6 +238,9 @@ export function validateModelId(apiConfiguration: ProviderSettings, routerModels case "litellm": modelId = apiConfiguration.litellmModelId break + case "archgw": + modelId = apiConfiguration.archgwModelId + break } if (!modelId) { @@ -302,3 +307,52 @@ export function validateApiConfigurationExcludingModelErrors( // skip model validation errors as they'll be shown in the model selector return undefined } + +/** + * Validates the ArchGw preference configuration YAML + * @param archgwPreferenceConfig The YAML string to validate + * @returns An object with validation results: { isValid, errorMessage } + */ +export function validateArchGwPreferenceConfig(archgwPreferenceConfig: string) { + try { + // Only validate if not empty + if (archgwPreferenceConfig.trim() !== "") { + const parsed = yaml.parse(archgwPreferenceConfig) + if (!Array.isArray(parsed)) { + return { + isValid: false, + errorMessage: i18next.t("settings:validation.routingConfig.invalidSchema"), + } + } + for (const item of parsed) { + if (typeof item !== "object" || typeof item.model !== "string") { + return { + isValid: false, + errorMessage: i18next.t("settings:validation.routingConfig.missingModelKey"), + } + } + if (!Array.isArray(item.routing_preferences)) { + return { + isValid: false, + errorMessage: i18next.t("settings:validation.routingConfig.missingPreferencesMap"), + } + } + for (const pref of item.routing_preferences) { + if ( + typeof pref !== "object" || + typeof pref.name !== "string" || + typeof pref.description !== "string" + ) { + return { + isValid: false, + errorMessage: i18next.t("settings:validation.routingConfig.invalidPreference"), + } + } + } + } + } + } catch (err: any) { + return { isValid: false, errorMessage: err.message || String(err) } + } + return { isValid: true, errorMessage: undefined } +}