diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index f5e9fc32bd..c071726d8a 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -194,6 +194,7 @@ export const SECRET_STATE_KEYS = [ "huggingFaceApiKey", "sambaNovaApiKey", "fireworksApiKey", + "ioIntelligenceApiKey", ] as const satisfies readonly (keyof ProviderSettings)[] export type SecretState = Pick diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index aebfd4dbe5..e6b5c4ca26 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -43,6 +43,7 @@ export const providerNames = [ "sambanova", "zai", "fireworks", + "io-intelligence", ] as const export const providerNamesSchema = z.enum(providerNames) @@ -276,6 +277,11 @@ const fireworksSchema = apiModelIdProviderModelSchema.extend({ fireworksApiKey: z.string().optional(), }) +const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({ + ioIntelligenceModelId: z.string().optional(), + ioIntelligenceApiKey: z.string().optional(), +}) + const defaultSchema = z.object({ apiProvider: z.undefined(), }) @@ -311,6 +317,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })), zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })), fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })), + ioIntelligenceSchema.merge(z.object({ apiProvider: z.literal("io-intelligence") })), defaultSchema, ]) @@ -346,6 +353,7 @@ export const providerSettingsSchema = z.object({ ...sambaNovaSchema.shape, ...zaiSchema.shape, ...fireworksSchema.shape, + ...ioIntelligenceSchema.shape, ...codebaseIndexProviderSchema.shape, }) @@ -371,6 +379,7 @@ export const MODEL_ID_KEYS: Partial[] = [ "requestyModelId", "litellmModelId", "huggingFaceModelId", + "ioIntelligenceModelId", ] export const getModelId = (settings: ProviderSettings): string | undefined => { diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 0ab27ea3dc..b7f1cd334e 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -8,6 +8,7 @@ export * from "./gemini.js" export * from "./glama.js" export * from "./groq.js" export * from "./huggingface.js" +export * from "./io-intelligence.js" export * from "./lite-llm.js" export * from "./lm-studio.js" export * from "./mistral.js" diff --git a/packages/types/src/providers/io-intelligence.ts b/packages/types/src/providers/io-intelligence.ts new file mode 100644 index 0000000000..a9b845393f --- /dev/null +++ b/packages/types/src/providers/io-intelligence.ts @@ -0,0 +1,44 @@ +import type { ModelInfo } from "../model.js" + +export type IOIntelligenceModelId = + | "deepseek-ai/DeepSeek-R1-0528" + | "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8" + | "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar" + | "openai/gpt-oss-120b" + +export const ioIntelligenceDefaultModelId: IOIntelligenceModelId = "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8" + +export const ioIntelligenceDefaultBaseUrl = "https://api.intelligence.io.solutions/api/v1" + +export const IO_INTELLIGENCE_CACHE_DURATION = 1000 * 60 * 60 // 1 hour + +export const ioIntelligenceModels = { + "deepseek-ai/DeepSeek-R1-0528": { + maxTokens: 8192, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + description: "DeepSeek R1 reasoning model", + }, + "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": { + maxTokens: 8192, + contextWindow: 430000, + supportsImages: true, + supportsPromptCache: false, + description: "Llama 4 Maverick 17B model", + }, + "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar": { + maxTokens: 8192, + contextWindow: 106000, + supportsImages: false, + supportsPromptCache: false, + description: "Qwen3 Coder 480B specialized for coding", + }, + "openai/gpt-oss-120b": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + description: "OpenAI GPT-OSS 120B model", + }, +} as const satisfies Record diff --git a/src/api/index.ts b/src/api/index.ts index 5e705a80d2..c29c230b06 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -32,6 +32,7 @@ import { LiteLLMHandler, ClaudeCodeHandler, SambaNovaHandler, + IOIntelligenceHandler, DoubaoHandler, ZAiHandler, FireworksHandler, @@ -137,6 +138,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { return new ZAiHandler(options) case "fireworks": return new FireworksHandler(options) + case "io-intelligence": + return new IOIntelligenceHandler(options) default: apiProvider satisfies "gemini-cli" | undefined return new AnthropicHandler(options) diff --git a/src/api/providers/__tests__/io-intelligence.spec.ts b/src/api/providers/__tests__/io-intelligence.spec.ts new file mode 100644 index 0000000000..56baf711cd --- /dev/null +++ b/src/api/providers/__tests__/io-intelligence.spec.ts @@ -0,0 +1,303 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" +import { IOIntelligenceHandler } from "../io-intelligence" +import type { ApiHandlerOptions } from "../../../shared/api" +import { Anthropic } from "@anthropic-ai/sdk" + +const mockCreate = vi.fn() + +// Mock OpenAI +vi.mock("openai", () => ({ + default: class MockOpenAI { + baseURL: string + apiKey: string + chat = { + completions: { + create: vi.fn(), + }, + } + constructor(options: any) { + this.baseURL = options.baseURL + this.apiKey = options.apiKey + this.chat.completions.create = mockCreate + } + }, +})) + +// Mock the fetcher functions +vi.mock("../fetchers/io-intelligence", () => ({ + getIOIntelligenceModels: vi.fn(), + getCachedIOIntelligenceModels: vi.fn(() => ({ + "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": { + maxTokens: 8192, + contextWindow: 430000, + description: "Llama 4 Maverick 17B model", + supportsImages: true, + supportsPromptCache: false, + }, + "deepseek-ai/DeepSeek-R1-0528": { + maxTokens: 8192, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + description: "DeepSeek R1 reasoning model", + }, + "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar": { + maxTokens: 4096, + contextWindow: 106000, + supportsImages: false, + supportsPromptCache: false, + description: "Qwen3 Coder 480B specialized for coding", + }, + "openai/gpt-oss-120b": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + description: "OpenAI GPT-OSS 120B model", + }, + })), +})) + +// Mock constants +vi.mock("../constants", () => ({ + DEFAULT_HEADERS: { "User-Agent": "roo-cline" }, +})) + +// Mock transform functions +vi.mock("../../transform/openai-format", () => ({ + convertToOpenAiMessages: vi.fn((messages) => messages), +})) + +describe("IOIntelligenceHandler", () => { + let handler: IOIntelligenceHandler + let mockOptions: ApiHandlerOptions + + beforeEach(() => { + vi.clearAllMocks() + mockOptions = { + ioIntelligenceApiKey: "test-api-key", + apiModelId: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + modelTemperature: 0.7, + includeMaxTokens: false, + modelMaxTokens: undefined, + } as ApiHandlerOptions + + mockCreate.mockImplementation(async () => ({ + [Symbol.asyncIterator]: async function* () { + yield { + choices: [ + { + delta: { content: "Test response" }, + index: 0, + }, + ], + usage: null, + } + yield { + choices: [ + { + delta: {}, + index: 0, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + }, + } + }, + })) + handler = new IOIntelligenceHandler(mockOptions) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it("should create OpenAI client with correct configuration", () => { + const ioIntelligenceApiKey = "test-io-intelligence-api-key" + const handler = new IOIntelligenceHandler({ ioIntelligenceApiKey }) + // Verify that the handler was created successfully + expect(handler).toBeInstanceOf(IOIntelligenceHandler) + expect(handler["client"]).toBeDefined() + // Verify the client has the expected properties + expect(handler["client"].baseURL).toBe("https://api.intelligence.io.solutions/api/v1") + expect(handler["client"].apiKey).toBe(ioIntelligenceApiKey) + }) + + it("should initialize with correct configuration", () => { + expect(handler).toBeInstanceOf(IOIntelligenceHandler) + expect(handler["client"]).toBeDefined() + expect(handler["options"]).toEqual({ + ...mockOptions, + apiKey: mockOptions.ioIntelligenceApiKey, + }) + }) + + it("should throw error when API key is missing", () => { + const optionsWithoutKey = { ...mockOptions } + delete optionsWithoutKey.ioIntelligenceApiKey + + expect(() => new IOIntelligenceHandler(optionsWithoutKey)).toThrow("IO Intelligence API key is required") + }) + + it("should handle streaming response correctly", async () => { + const mockStream = [ + { + choices: [{ delta: { content: "Hello" } }], + usage: null, + }, + { + choices: [{ delta: { content: " world" } }], + usage: null, + }, + { + choices: [{ delta: {} }], + usage: { prompt_tokens: 10, completion_tokens: 5 }, + }, + ] + + mockCreate.mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + for (const chunk of mockStream) { + yield chunk + } + }, + }) + + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }] + + const stream = handler.createMessage("System prompt", messages) + const results = [] + + for await (const chunk of stream) { + results.push(chunk) + } + + expect(results).toHaveLength(3) + expect(results[0]).toEqual({ type: "text", text: "Hello" }) + expect(results[1]).toEqual({ type: "text", text: " world" }) + expect(results[2]).toEqual({ + type: "usage", + inputTokens: 10, + outputTokens: 5, + }) + }) + + it("completePrompt method should return text from IO Intelligence API", async () => { + const expectedResponse = "This is a test response from IO Intelligence" + mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] }) + const result = await handler.completePrompt("test prompt") + expect(result).toBe(expectedResponse) + }) + + it("should handle errors in completePrompt", async () => { + const errorMessage = "IO Intelligence API error" + mockCreate.mockRejectedValueOnce(new Error(errorMessage)) + await expect(handler.completePrompt("test prompt")).rejects.toThrow( + `IO Intelligence completion error: ${errorMessage}`, + ) + }) + + it("createMessage should yield text content from stream", async () => { + const testContent = "This is test content from IO Intelligence stream" + + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vi + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: testContent } }] }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "text", text: testContent }) + }) + + it("createMessage should yield usage data from stream", async () => { + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vi + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 }) + }) + + it("should return model info from cache when available", () => { + const model = handler.getModel() + expect(model.id).toBe("meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8") + expect(model.info).toEqual({ + maxTokens: 8192, + contextWindow: 430000, + description: "Llama 4 Maverick 17B model", + supportsImages: true, + supportsPromptCache: false, + }) + }) + + it("should return fallback model info when not in cache", () => { + const handlerWithUnknownModel = new IOIntelligenceHandler({ + ...mockOptions, + apiModelId: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + }) + const model = handlerWithUnknownModel.getModel() + expect(model.id).toBe("meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8") + expect(model.info).toEqual({ + maxTokens: 8192, + contextWindow: 430000, + description: "Llama 4 Maverick 17B model", + supportsImages: true, + supportsPromptCache: false, + }) + }) + + it("should use default model when no model is specified", () => { + const handlerWithoutModel = new IOIntelligenceHandler({ + ...mockOptions, + apiModelId: undefined, + }) + const model = handlerWithoutModel.getModel() + expect(model.id).toBe("meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8") + }) + + it("should handle empty response from completePrompt", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [{ message: { content: null } }], + }) + + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("") + }) + + it("should handle missing choices in completePrompt response", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [], + }) + + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("") + }) +}) diff --git a/src/api/providers/fetchers/__tests__/modelCache.spec.ts b/src/api/providers/fetchers/__tests__/modelCache.spec.ts index 69369a2ce8..21f5ce8bff 100644 --- a/src/api/providers/fetchers/__tests__/modelCache.spec.ts +++ b/src/api/providers/fetchers/__tests__/modelCache.spec.ts @@ -24,6 +24,7 @@ vi.mock("../openrouter") vi.mock("../requesty") vi.mock("../glama") vi.mock("../unbound") +vi.mock("../io-intelligence") // Then imports import type { Mock } from "vitest" @@ -33,15 +34,18 @@ import { getOpenRouterModels } from "../openrouter" import { getRequestyModels } from "../requesty" import { getGlamaModels } from "../glama" import { getUnboundModels } from "../unbound" +import { getIOIntelligenceModels } from "../io-intelligence" const mockGetLiteLLMModels = getLiteLLMModels as Mock const mockGetOpenRouterModels = getOpenRouterModels as Mock const mockGetRequestyModels = getRequestyModels as Mock const mockGetGlamaModels = getGlamaModels as Mock const mockGetUnboundModels = getUnboundModels as Mock +const mockGetIOIntelligenceModels = getIOIntelligenceModels as Mock const DUMMY_REQUESTY_KEY = "requesty-key-for-testing" const DUMMY_UNBOUND_KEY = "unbound-key-for-testing" +const DUMMY_IOINTELLIGENCE_KEY = "io-intelligence-key-for-testing" describe("getModels with new GetModelsOptions", () => { beforeEach(() => { @@ -137,6 +141,23 @@ describe("getModels with new GetModelsOptions", () => { expect(result).toEqual(mockModels) }) + it("calls IOIntelligenceModels for IO-Intelligence provider", async () => { + const mockModels = { + "io-intelligence/model": { + maxTokens: 4096, + contextWindow: 8192, + supportsPromptCache: false, + description: "IO Intelligence Model", + }, + } + mockGetIOIntelligenceModels.mockResolvedValue(mockModels) + + const result = await getModels({ provider: "io-intelligence", apiKey: DUMMY_IOINTELLIGENCE_KEY }) + + expect(mockGetIOIntelligenceModels).toHaveBeenCalled() + expect(result).toEqual(mockModels) + }) + it("handles errors and re-throws them", async () => { const expectedError = new Error("LiteLLM connection failed") mockGetLiteLLMModels.mockRejectedValue(expectedError) diff --git a/src/api/providers/fetchers/io-intelligence.ts b/src/api/providers/fetchers/io-intelligence.ts new file mode 100644 index 0000000000..326cefb0cc --- /dev/null +++ b/src/api/providers/fetchers/io-intelligence.ts @@ -0,0 +1,189 @@ +import axios from "axios" +import { z } from "zod" +import type { ModelInfo } from "@roo-code/types" +import { IO_INTELLIGENCE_CACHE_DURATION } from "@roo-code/types" +import type { ModelRecord } from "../../../shared/api" + +/** + * IO Intelligence Model Schema + */ +const ioIntelligenceModelSchema = z.object({ + id: z.string(), + object: z.literal("model"), + created: z.number(), + owned_by: z.string(), + root: z.string().nullable().optional(), + parent: z.string().nullable().optional(), + max_model_len: z.number().nullable().optional(), + permission: z.array( + z.object({ + id: z.string(), + object: z.literal("model_permission"), + created: z.number(), + allow_create_engine: z.boolean(), + allow_sampling: z.boolean(), + allow_logprobs: z.boolean(), + allow_search_indices: z.boolean(), + allow_view: z.boolean(), + allow_fine_tuning: z.boolean(), + organization: z.string(), + group: z.string().nullable(), + is_blocking: z.boolean(), + }), + ), +}) + +export type IOIntelligenceModel = z.infer + +/** + * IO Intelligence API Response Schema + */ +const ioIntelligenceApiResponseSchema = z.object({ + object: z.literal("list"), + data: z.array(ioIntelligenceModelSchema), +}) + +type IOIntelligenceApiResponse = z.infer + +/** + * Cache entry for storing fetched models + */ +interface CacheEntry { + data: ModelRecord + timestamp: number +} + +let cache: CacheEntry | null = null + +/** + * Model context length mapping based on the documentation + * 1 + */ +const MODEL_CONTEXT_LENGTHS: Record = { + "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": 430000, + "deepseek-ai/DeepSeek-R1-0528": 128000, + "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar": 106000, + "openai/gpt-oss-120b": 131072, +} + +/** + * Vision models that support images + */ +const VISION_MODELS = new Set([ + "Qwen/Qwen2.5-VL-32B-Instruct", + "meta-llama/Llama-3.2-90B-Vision-Instruct", + "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", +]) + +/** + * Parse an IO Intelligence model into ModelInfo format + */ +function parseIOIntelligenceModel(model: IOIntelligenceModel): ModelInfo { + const contextLength = MODEL_CONTEXT_LENGTHS[model.id] || 8192 + // Cap maxTokens at 32k for very large context windows, or 20% of context length, whichever is smaller + const maxTokens = Math.min(contextLength, Math.ceil(contextLength * 0.2), 32768) + const supportsImages = VISION_MODELS.has(model.id) + + return { + maxTokens, + contextWindow: contextLength, + supportsImages, + supportsPromptCache: false, + supportsComputerUse: false, + description: `${model.id} via IO Intelligence`, + } +} + +/** + * Fetches available models from IO Intelligence + * 1 + */ +export async function getIOIntelligenceModels(apiKey?: string): Promise { + const now = Date.now() + + // Check cache + if (cache && now - cache.timestamp < IO_INTELLIGENCE_CACHE_DURATION) { + return cache.data + } + + const models: ModelRecord = {} + + try { + const headers: Record = { + "Content-Type": "application/json", + } + + // Add authorization header if API key is provided + if (apiKey) { + headers.Authorization = `Bearer ${apiKey}` + } else { + console.error("IO Intelligence API key is required") + throw new Error("IO Intelligence API key is required") + } + + const response = await axios.get( + "https://api.intelligence.io.solutions/api/v1/models", + { + headers, + timeout: 10000, // 10 second timeout + }, + ) + + const result = ioIntelligenceApiResponseSchema.safeParse(response.data) + + if (!result.success) { + console.error("IO Intelligence models response validation failed:", result.error.format()) + throw new Error("Invalid response format from IO Intelligence API") + } + + for (const model of result.data.data) { + models[model.id] = parseIOIntelligenceModel(model) + } + + // Update cache + cache = { + data: models, + timestamp: now, + } + + return models + } catch (error) { + console.error("Error fetching IO Intelligence models:", error) + + // Return cached data if available + if (cache) { + return cache.data + } + + // Re-throw with more context + if (axios.isAxiosError(error)) { + if (error.response) { + throw new Error( + `Failed to fetch IO Intelligence models: ${error.response.status} ${error.response.statusText}`, + ) + } else if (error.request) { + throw new Error( + "Failed to fetch IO Intelligence models: No response from server. Check your internet connection.", + ) + } + } + + throw new Error( + `Failed to fetch IO Intelligence models: ${error instanceof Error ? error.message : "Unknown error"}`, + ) + } +} + +/** + * Get cached models without making an API request + */ +export function getCachedIOIntelligenceModels(): ModelRecord | null { + return cache?.data || null +} + +/** + * Clear the cache + */ +export function clearIOIntelligenceCache(): void { + cache = null +} diff --git a/src/api/providers/fetchers/modelCache.ts b/src/api/providers/fetchers/modelCache.ts index dd6bc01ba1..a21e75ded9 100644 --- a/src/api/providers/fetchers/modelCache.ts +++ b/src/api/providers/fetchers/modelCache.ts @@ -17,7 +17,7 @@ import { getLiteLLMModels } from "./litellm" import { GetModelsOptions } from "../../../shared/api" import { getOllamaModels } from "./ollama" import { getLMStudioModels } from "./lmstudio" - +import { getIOIntelligenceModels } from "./io-intelligence" const memoryCache = new NodeCache({ stdTTL: 5 * 60, checkperiod: 5 * 60 }) async function writeModels(router: RouterName, data: ModelRecord) { @@ -78,6 +78,9 @@ export const getModels = async (options: GetModelsOptions): Promise case "lmstudio": models = await getLMStudioModels(options.baseUrl) break + case "io-intelligence": + models = await getIOIntelligenceModels(options.apiKey) + break default: { // Ensures router is exhaustively checked if RouterName is a strict union const exhaustiveCheck: never = provider diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index 890999aa25..736da82d51 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -13,6 +13,7 @@ export { GlamaHandler } from "./glama" export { GroqHandler } from "./groq" export { HuggingFaceHandler } from "./huggingface" export { HumanRelayHandler } from "./human-relay" +export { IOIntelligenceHandler } from "./io-intelligence" export { LiteLLMHandler } from "./lite-llm" export { LmStudioHandler } from "./lm-studio" export { MistralHandler } from "./mistral" diff --git a/src/api/providers/io-intelligence.ts b/src/api/providers/io-intelligence.ts new file mode 100644 index 0000000000..cbfb3129f9 --- /dev/null +++ b/src/api/providers/io-intelligence.ts @@ -0,0 +1,42 @@ +import { ioIntelligenceDefaultModelId, ioIntelligenceModels, type IOIntelligenceModelId } from "@roo-code/types" + +import type { ApiHandlerOptions } from "../../shared/api" +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" + +export class IOIntelligenceHandler extends BaseOpenAiCompatibleProvider { + constructor(options: ApiHandlerOptions) { + if (!options.ioIntelligenceApiKey) { + throw new Error("IO Intelligence API key is required") + } + + super({ + ...options, + providerName: "IO Intelligence", + baseURL: "https://api.intelligence.io.solutions/api/v1", + defaultProviderModelId: ioIntelligenceDefaultModelId, + providerModels: ioIntelligenceModels, + defaultTemperature: 0.7, + apiKey: options.ioIntelligenceApiKey, + }) + } + override getModel() { + const modelId = this.options.ioIntelligenceModelId || (ioIntelligenceDefaultModelId as IOIntelligenceModelId) + const modelInfo = + this.providerModels[modelId as IOIntelligenceModelId] ?? this.providerModels[ioIntelligenceDefaultModelId] + + if (modelInfo) { + return { id: modelId as IOIntelligenceModelId, info: modelInfo } + } + + // Return the requested model ID even if not found, with fallback info + return { + id: modelId as IOIntelligenceModelId, + info: { + maxTokens: 8192, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + }, + } + } +} diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index f5dc6a467f..367ee07670 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -548,6 +548,15 @@ export const webviewMessageHandler = async ( { key: "unbound", options: { provider: "unbound", apiKey: apiConfiguration.unboundApiKey } }, ] + // Add IO Intelligence if API key is provided + const ioIntelligenceApiKey = apiConfiguration.ioIntelligenceApiKey + if (ioIntelligenceApiKey) { + modelFetchPromises.push({ + key: "io-intelligence", + options: { provider: "io-intelligence", apiKey: ioIntelligenceApiKey }, + }) + } + // Don't fetch Ollama and LM Studio models by default anymore // They have their own specific handlers: requestOllamaModels and requestLmStudioModels diff --git a/src/shared/ProfileValidator.ts b/src/shared/ProfileValidator.ts index 3dc8025ff8..5a7c1da8bd 100644 --- a/src/shared/ProfileValidator.ts +++ b/src/shared/ProfileValidator.ts @@ -87,6 +87,8 @@ export class ProfileValidator { return profile.ollamaModelId case "requesty": return profile.requestyModelId + case "io-intelligence": + return profile.ioIntelligenceModelId case "human-relay": case "fake-ai": default: diff --git a/src/shared/__tests__/ProfileValidator.spec.ts b/src/shared/__tests__/ProfileValidator.spec.ts index fa055a8157..f606fdce99 100644 --- a/src/shared/__tests__/ProfileValidator.spec.ts +++ b/src/shared/__tests__/ProfileValidator.spec.ts @@ -229,6 +229,22 @@ describe("ProfileValidator", () => { expect(ProfileValidator.isProfileAllowed(profile, allowList)).toBe(true) }) + // Test for io-intelligence provider which uses ioIntelligenceModelId + it(`should extract ioIntelligenceModelId for io-intelligence provider`, () => { + const allowList: OrganizationAllowList = { + allowAll: false, + providers: { + "io-intelligence": { allowAll: false, models: ["test-model"] }, + }, + } + const profile: ProviderSettings = { + apiProvider: "io-intelligence" as any, + ioIntelligenceModelId: "test-model", + } + + expect(ProfileValidator.isProfileAllowed(profile, allowList)).toBe(true) + }) + it("should extract vsCodeLmModelSelector.id for vscode-lm provider", () => { const allowList: OrganizationAllowList = { allowAll: false, diff --git a/src/shared/api.ts b/src/shared/api.ts index e9b57af3c1..01f8fa2dbf 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -18,7 +18,16 @@ 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", + "io-intelligence", +] as const export type RouterName = (typeof routerNames)[number] @@ -121,3 +130,4 @@ export type GetModelsOptions = | { provider: "litellm"; apiKey: string; baseUrl: string } | { provider: "ollama"; baseUrl?: string } | { provider: "lmstudio"; baseUrl?: string } + | { provider: "io-intelligence"; apiKey: string } diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 70a58f03bf..dcdf072a11 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -31,6 +31,7 @@ import { internationalZAiDefaultModelId, mainlandZAiDefaultModelId, fireworksDefaultModelId, + ioIntelligenceDefaultModelId, } from "@roo-code/types" import { vscode } from "@src/utils/vscode" @@ -68,6 +69,7 @@ import { Glama, Groq, HuggingFace, + IOIntelligence, LMStudio, LiteLLM, Mistral, @@ -320,6 +322,7 @@ const ApiOptions = ({ : internationalZAiDefaultModelId, }, fireworks: { field: "apiModelId", default: fireworksDefaultModelId }, + "io-intelligence": { field: "ioIntelligenceModelId", default: ioIntelligenceDefaultModelId }, openai: { field: "openAiModelId" }, ollama: { field: "ollamaModelId" }, lmstudio: { field: "lmStudioModelId" }, @@ -548,6 +551,15 @@ const ApiOptions = ({ )} + {selectedProvider === "io-intelligence" && ( + + )} + {selectedProvider === "human-relay" && ( <>
diff --git a/webview-ui/src/components/settings/ModelPicker.tsx b/webview-ui/src/components/settings/ModelPicker.tsx index c8445766f1..cdfd8c7c9d 100644 --- a/webview-ui/src/components/settings/ModelPicker.tsx +++ b/webview-ui/src/components/settings/ModelPicker.tsx @@ -28,7 +28,13 @@ import { ApiErrorMessage } from "./ApiErrorMessage" type ModelIdKey = keyof Pick< ProviderSettings, - "glamaModelId" | "openRouterModelId" | "unboundModelId" | "requestyModelId" | "openAiModelId" | "litellmModelId" + | "glamaModelId" + | "openRouterModelId" + | "unboundModelId" + | "requestyModelId" + | "openAiModelId" + | "litellmModelId" + | "ioIntelligenceModelId" > interface ModelPickerProps { diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index 90192f372b..5882b1bf4a 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -69,4 +69,5 @@ export const PROVIDERS = [ { value: "sambanova", label: "SambaNova" }, { value: "zai", label: "Z AI" }, { value: "fireworks", label: "Fireworks AI" }, + { value: "io-intelligence", label: "IO Intelligence" }, ].sort((a, b) => a.label.localeCompare(b.label)) diff --git a/webview-ui/src/components/settings/providers/IOIntelligence.tsx b/webview-ui/src/components/settings/providers/IOIntelligence.tsx new file mode 100644 index 0000000000..cbb23ba641 --- /dev/null +++ b/webview-ui/src/components/settings/providers/IOIntelligence.tsx @@ -0,0 +1,72 @@ +import { useCallback } from "react" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types" +import { ioIntelligenceDefaultModelId, ioIntelligenceModels } from "@roo-code/types" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" +import { useExtensionState } from "@src/context/ExtensionStateContext" +import { ModelPicker } from "../ModelPicker" + +import { inputEventTransform } from "../transforms" + +type IOIntelligenceProps = { + apiConfiguration: ProviderSettings + setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void + organizationAllowList: OrganizationAllowList + modelValidationError?: string +} + +export const IOIntelligence = ({ + apiConfiguration, + setApiConfigurationField, + organizationAllowList, + modelValidationError, +}: IOIntelligenceProps) => { + const { t } = useAppTranslation() + const { routerModels } = useExtensionState() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ProviderSettings[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.ioIntelligenceApiKey && ( + + {t("settings:providers.getIoIntelligenceApiKey")} + + )} + + + ) +} diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts index e8428eb66c..f054780b06 100644 --- a/webview-ui/src/components/settings/providers/index.ts +++ b/webview-ui/src/components/settings/providers/index.ts @@ -9,6 +9,7 @@ export { Gemini } from "./Gemini" export { Glama } from "./Glama" export { Groq } from "./Groq" export { HuggingFace } from "./HuggingFace" +export { IOIntelligence } from "./IOIntelligence" export { LMStudio } from "./LMStudio" export { Mistral } from "./Mistral" export { Moonshot } from "./Moonshot" diff --git a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts index 5fefabf59e..76c9878c9d 100644 --- a/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts +++ b/webview-ui/src/components/ui/hooks/__tests__/useSelectedModel.spec.ts @@ -58,6 +58,7 @@ describe("useSelectedModel", () => { glama: {}, unbound: {}, litellm: {}, + "io-intelligence": {}, }, isLoading: false, isError: false, @@ -110,6 +111,7 @@ describe("useSelectedModel", () => { glama: {}, unbound: {}, litellm: {}, + "io-intelligence": {}, }, isLoading: false, isError: false, @@ -164,6 +166,7 @@ describe("useSelectedModel", () => { glama: {}, unbound: {}, litellm: {}, + "io-intelligence": {}, }, isLoading: false, isError: false, @@ -219,6 +222,7 @@ describe("useSelectedModel", () => { glama: {}, unbound: {}, litellm: {}, + "io-intelligence": {}, }, isLoading: false, isError: false, @@ -263,6 +267,7 @@ describe("useSelectedModel", () => { glama: {}, unbound: {}, litellm: {}, + "io-intelligence": {}, }, isLoading: false, isError: false, @@ -310,7 +315,7 @@ describe("useSelectedModel", () => { it("should return loading state when open router model providers are loading", () => { mockUseRouterModels.mockReturnValue({ - data: { openrouter: {}, requesty: {}, glama: {}, unbound: {}, litellm: {} }, + data: { openrouter: {}, requesty: {}, glama: {}, unbound: {}, litellm: {}, "io-intelligence": {} }, isLoading: false, isError: false, } as any) @@ -379,6 +384,7 @@ describe("useSelectedModel", () => { glama: {}, unbound: {}, litellm: {}, + "io-intelligence": {}, }, isLoading: false, isError: false, @@ -417,6 +423,7 @@ describe("useSelectedModel", () => { glama: {}, unbound: {}, litellm: {}, + "io-intelligence": {}, }, isLoading: false, isError: false, diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 2ccf9d4071..c67ec796b6 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -46,6 +46,8 @@ import { mainlandZAiModels, fireworksModels, fireworksDefaultModelId, + ioIntelligenceDefaultModelId, + ioIntelligenceModels, } from "@roo-code/types" import type { ModelRecord, RouterModels } from "@roo/api" @@ -277,6 +279,12 @@ function getSelectedModel({ const info = fireworksModels[id as keyof typeof fireworksModels] return { id, info } } + case "io-intelligence": { + const id = apiConfiguration.ioIntelligenceModelId ?? ioIntelligenceDefaultModelId + const info = + routerModels["io-intelligence"]?.[id] ?? ioIntelligenceModels[id as keyof typeof ioIntelligenceModels] + return { id, info } + } // case "anthropic": // case "human-relay": // case "fake-ai": diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index bcdf30a803..1b44356ef8 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Obtenir clau API de Chutes", "fireworksApiKey": "Clau API de Fireworks", "getFireworksApiKey": "Obtenir clau API de Fireworks", + "ioIntelligenceApiKey": "Clau API d'IO Intelligence", + "ioIntelligenceApiKeyPlaceholder": "Introdueix la teva clau d'API de IO Intelligence", + "getIoIntelligenceApiKey": "Obtenir clau API d'IO Intelligence", "deepSeekApiKey": "Clau API de DeepSeek", "getDeepSeekApiKey": "Obtenir clau API de DeepSeek", "doubaoApiKey": "Clau API de Doubao", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index f434c5f413..28407366d7 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -265,6 +265,9 @@ "getChutesApiKey": "Chutes API-Schlüssel erhalten", "fireworksApiKey": "Fireworks API-Schlüssel", "getFireworksApiKey": "Fireworks API-Schlüssel erhalten", + "ioIntelligenceApiKey": "IO Intelligence API-Schlüssel", + "ioIntelligenceApiKeyPlaceholder": "Gib deinen IO Intelligence API-Schlüssel ein", + "getIoIntelligenceApiKey": "IO Intelligence API-Schlüssel erhalten", "deepSeekApiKey": "DeepSeek API-Schlüssel", "getDeepSeekApiKey": "DeepSeek API-Schlüssel erhalten", "moonshotApiKey": "Moonshot API-Schlüssel", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index eb893d6c51..b45af589e5 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -262,6 +262,9 @@ "getChutesApiKey": "Get Chutes API Key", "fireworksApiKey": "Fireworks API Key", "getFireworksApiKey": "Get Fireworks API Key", + "ioIntelligenceApiKey": "IO Intelligence API Key", + "ioIntelligenceApiKeyPlaceholder": "Enter your IO Intelligence API key", + "getIoIntelligenceApiKey": "Get IO Intelligence API Key", "deepSeekApiKey": "DeepSeek API Key", "getDeepSeekApiKey": "Get DeepSeek API Key", "doubaoApiKey": "Doubao API Key", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index f5fff79f6a..637537a789 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Obtener clave API de Chutes", "fireworksApiKey": "Clave API de Fireworks", "getFireworksApiKey": "Obtener clave API de Fireworks", + "ioIntelligenceApiKey": "Clave API de IO Intelligence", + "ioIntelligenceApiKeyPlaceholder": "Introduce tu clave de API de IO Intelligence", + "getIoIntelligenceApiKey": "Obtener clave API de IO Intelligence", "deepSeekApiKey": "Clave API de DeepSeek", "getDeepSeekApiKey": "Obtener clave API de DeepSeek", "doubaoApiKey": "Clave API de Doubao", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 3e2749c36c..856ef37dcf 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Obtenir la clé API Chutes", "fireworksApiKey": "Clé API Fireworks", "getFireworksApiKey": "Obtenir la clé API Fireworks", + "ioIntelligenceApiKey": "Clé API IO Intelligence", + "ioIntelligenceApiKeyPlaceholder": "Saisissez votre clé d'API IO Intelligence", + "getIoIntelligenceApiKey": "Obtenir la clé API IO Intelligence", "deepSeekApiKey": "Clé API DeepSeek", "getDeepSeekApiKey": "Obtenir la clé API DeepSeek", "doubaoApiKey": "Clé API Doubao", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 4fecd0ea54..7131ad486f 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Chutes API कुंजी प्राप्त करें", "fireworksApiKey": "Fireworks API कुंजी", "getFireworksApiKey": "Fireworks API कुंजी प्राप्त करें", + "ioIntelligenceApiKey": "IO Intelligence API कुंजी", + "ioIntelligenceApiKeyPlaceholder": "अपना आईओ इंटेलिजेंस एपीआई कुंजी दर्ज करें", + "getIoIntelligenceApiKey": "IO Intelligence API कुंजी प्राप्त करें", "deepSeekApiKey": "DeepSeek API कुंजी", "getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें", "doubaoApiKey": "डौबाओ API कुंजी", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 576784dc77..19e298eb6c 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -267,6 +267,9 @@ "getChutesApiKey": "Dapatkan Chutes API Key", "fireworksApiKey": "Fireworks API Key", "getFireworksApiKey": "Dapatkan Fireworks API Key", + "ioIntelligenceApiKey": "IO Intelligence API Key", + "ioIntelligenceApiKeyPlaceholder": "Masukkan kunci API IO Intelligence Anda", + "getIoIntelligenceApiKey": "Dapatkan IO Intelligence API Key", "deepSeekApiKey": "DeepSeek API Key", "getDeepSeekApiKey": "Dapatkan DeepSeek API Key", "doubaoApiKey": "Kunci API Doubao", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 1a657c5a30..9ce898bc08 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Ottieni chiave API Chutes", "fireworksApiKey": "Chiave API Fireworks", "getFireworksApiKey": "Ottieni chiave API Fireworks", + "ioIntelligenceApiKey": "Chiave API IO Intelligence", + "ioIntelligenceApiKeyPlaceholder": "Inserisci la tua chiave API IO Intelligence", + "getIoIntelligenceApiKey": "Ottieni chiave API IO Intelligence", "deepSeekApiKey": "Chiave API DeepSeek", "getDeepSeekApiKey": "Ottieni chiave API DeepSeek", "doubaoApiKey": "Chiave API Doubao", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 9c35c02d64..444ab855cf 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Chutes APIキーを取得", "fireworksApiKey": "Fireworks APIキー", "getFireworksApiKey": "Fireworks APIキーを取得", + "ioIntelligenceApiKey": "IO Intelligence APIキー", + "ioIntelligenceApiKeyPlaceholder": "IO Intelligence APIキーを入力してください", + "getIoIntelligenceApiKey": "IO Intelligence APIキーを取得", "deepSeekApiKey": "DeepSeek APIキー", "getDeepSeekApiKey": "DeepSeek APIキーを取得", "doubaoApiKey": "Doubao APIキー", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index ee4d6a1889..f9d8432528 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Chutes API 키 받기", "fireworksApiKey": "Fireworks API 키", "getFireworksApiKey": "Fireworks API 키 받기", + "ioIntelligenceApiKey": "IO Intelligence API 키", + "ioIntelligenceApiKeyPlaceholder": "IO Intelligence API 키를 입력하세요", + "getIoIntelligenceApiKey": "IO Intelligence API 키 받기", "deepSeekApiKey": "DeepSeek API 키", "getDeepSeekApiKey": "DeepSeek API 키 받기", "doubaoApiKey": "Doubao API 키", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 9a514520eb..bdb5a36ebc 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Chutes API-sleutel ophalen", "fireworksApiKey": "Fireworks API-sleutel", "getFireworksApiKey": "Fireworks API-sleutel ophalen", + "ioIntelligenceApiKey": "IO Intelligence API-sleutel", + "ioIntelligenceApiKeyPlaceholder": "Voer je IO Intelligence API-sleutel in", + "getIoIntelligenceApiKey": "IO Intelligence API-sleutel ophalen", "deepSeekApiKey": "DeepSeek API-sleutel", "getDeepSeekApiKey": "DeepSeek API-sleutel ophalen", "doubaoApiKey": "Doubao API-sleutel", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 6f2ff53e0b..d36da8ace4 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Uzyskaj klucz API Chutes", "fireworksApiKey": "Klucz API Fireworks", "getFireworksApiKey": "Uzyskaj klucz API Fireworks", + "ioIntelligenceApiKey": "Klucz API IO Intelligence", + "ioIntelligenceApiKeyPlaceholder": "Wprowadź swój klucz API IO Intelligence", + "getIoIntelligenceApiKey": "Uzyskaj klucz API IO Intelligence", "deepSeekApiKey": "Klucz API DeepSeek", "getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek", "doubaoApiKey": "Klucz API Doubao", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 8dd6cec52b..6f5b839b46 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Obter chave de API Chutes", "fireworksApiKey": "Chave de API Fireworks", "getFireworksApiKey": "Obter chave de API Fireworks", + "ioIntelligenceApiKey": "Chave de API IO Intelligence", + "ioIntelligenceApiKeyPlaceholder": "Insira sua chave de API da IO Intelligence", + "getIoIntelligenceApiKey": "Obter chave de API IO Intelligence", "deepSeekApiKey": "Chave de API DeepSeek", "getDeepSeekApiKey": "Obter chave de API DeepSeek", "doubaoApiKey": "Chave de API Doubao", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index b758c86a94..7dc6645e1b 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Получить Chutes API-ключ", "fireworksApiKey": "Fireworks API-ключ", "getFireworksApiKey": "Получить Fireworks API-ключ", + "ioIntelligenceApiKey": "IO Intelligence API-ключ", + "ioIntelligenceApiKeyPlaceholder": "Введите свой ключ API IO Intelligence", + "getIoIntelligenceApiKey": "Получить IO Intelligence API-ключ", "deepSeekApiKey": "DeepSeek API-ключ", "getDeepSeekApiKey": "Получить DeepSeek API-ключ", "doubaoApiKey": "Doubao API-ключ", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 542b2b2585..8493fd19de 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Chutes API Anahtarı Al", "fireworksApiKey": "Fireworks API Anahtarı", "getFireworksApiKey": "Fireworks API Anahtarı Al", + "ioIntelligenceApiKey": "IO Intelligence API Anahtarı", + "ioIntelligenceApiKeyPlaceholder": "IO Intelligence API anahtarınızı girin", + "getIoIntelligenceApiKey": "IO Intelligence API Anahtarı Al", "deepSeekApiKey": "DeepSeek API Anahtarı", "getDeepSeekApiKey": "DeepSeek API Anahtarı Al", "doubaoApiKey": "Doubao API Anahtarı", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 8b1dd793fe..776c7b7f72 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "Lấy khóa API Chutes", "fireworksApiKey": "Khóa API Fireworks", "getFireworksApiKey": "Lấy khóa API Fireworks", + "ioIntelligenceApiKey": "Khóa API IO Intelligence", + "ioIntelligenceApiKeyPlaceholder": "Nhập khóa API IO Intelligence của bạn", + "getIoIntelligenceApiKey": "Lấy khóa API IO Intelligence", "deepSeekApiKey": "Khóa API DeepSeek", "getDeepSeekApiKey": "Lấy khóa API DeepSeek", "doubaoApiKey": "Khóa API Doubao", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index e475fd7baa..9f4a345e14 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "获取 Chutes API 密钥", "fireworksApiKey": "Fireworks API 密钥", "getFireworksApiKey": "获取 Fireworks API 密钥", + "ioIntelligenceApiKey": "IO Intelligence API 密钥", + "ioIntelligenceApiKeyPlaceholder": "输入您的 IO Intelligence API 密钥", + "getIoIntelligenceApiKey": "获取 IO Intelligence API 密钥", "deepSeekApiKey": "DeepSeek API 密钥", "getDeepSeekApiKey": "获取 DeepSeek API 密钥", "doubaoApiKey": "豆包 API 密钥", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 9242861eaa..99c423abdd 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -263,6 +263,9 @@ "getChutesApiKey": "取得 Chutes API 金鑰", "fireworksApiKey": "Fireworks API 金鑰", "getFireworksApiKey": "取得 Fireworks API 金鑰", + "ioIntelligenceApiKey": "IO Intelligence API 金鑰", + "ioIntelligenceApiKeyPlaceholder": "輸入您的 IO Intelligence API 金鑰", + "getIoIntelligenceApiKey": "取得 IO Intelligence API 金鑰", "deepSeekApiKey": "DeepSeek API 金鑰", "getDeepSeekApiKey": "取得 DeepSeek API 金鑰", "doubaoApiKey": "豆包 API 金鑰", diff --git a/webview-ui/src/utils/__tests__/validate.test.ts b/webview-ui/src/utils/__tests__/validate.test.ts index 3a60c27f8a..9e693544b9 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: {}, + "io-intelligence": {}, } const allowAllOrganization: OrganizationAllowList = { @@ -185,5 +186,25 @@ describe("Model Validation Functions", () => { ) expect(result).toBeUndefined() // Should exclude model-specific org errors }) + + it("returns undefined for valid IO Intelligence model", () => { + const config: ProviderSettings = { + apiProvider: "io-intelligence", + glamaModelId: "valid-model", + } + + const result = getModelValidationError(config, mockRouterModels, allowAllOrganization) + expect(result).toBeUndefined() + }) + + it("returns error for invalid IO Intelligence model", () => { + const config: ProviderSettings = { + apiProvider: "io-intelligence", + glamaModelId: "invalid-model", + } + + const result = getModelValidationError(config, mockRouterModels, allowAllOrganization) + expect(result).toBeUndefined() + }) }) }) diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index b39060e665..70caf187f0 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -120,6 +120,11 @@ function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): stri return i18next.t("settings:validation.apiKey") } break + case "io-intelligence": + if (!apiConfiguration.ioIntelligenceApiKey) { + return i18next.t("settings:validation.apiKey") + } + break } return undefined @@ -186,6 +191,8 @@ function getModelIdForProvider(apiConfiguration: ProviderSettings, provider: str return apiConfiguration.vsCodeLmModelSelector?.id case "huggingface": return apiConfiguration.huggingFaceModelId + case "io-intelligence": + return apiConfiguration.ioIntelligenceModelId default: return apiConfiguration.apiModelId } @@ -256,6 +263,9 @@ export function validateModelId(apiConfiguration: ProviderSettings, routerModels case "litellm": modelId = apiConfiguration.litellmModelId break + case "io-intelligence": + modelId = apiConfiguration.ioIntelligenceModelId + break } if (!modelId) {