Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-poe-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": minor
---

Add Poe as a supported API provider
29 changes: 29 additions & 0 deletions apps/kilocode-docs/docs/providers/poe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
sidebar_label: Poe
---

# Using Poe With Kilo Code

Kilo Code supports accessing models through the [Poe](https://www.poe.com/) platform. Poe provides an easy and optimized API for interacting with 200+ large language models (LLMs).

**Website:** [https://www.poe.com/](https://www.poe.com/)

## Getting an API Key

1. **Sign Up/Sign In:** Go to the [Poe website](https://www.poe.com/) and create an account or sign in.
2. **Get API Key:** You can get an API key from the [API Management](https://poe.com/api_key) section of your Poe dashboard.

## Supported Models

Poe provides access to a wide range of models. Kilo Code will automatically fetch the latest list of available models. You can see the full list of available models on the [Model List](https://poe.com/explore?category=Official) page.

## Configuration in Kilo Code

1. **Open Kilo Code Settings:** Click the gear icon (<Codicon name="gear" />) in the Kilo Code panel.
2. **Select Provider:** Choose "Poe" from the "API Provider" dropdown.
3. **Enter API Key:** Paste your Poe API key into the "Poe API Key" field.
4. **Select Model:** Choose your desired model from the "Model" dropdown.

## Relevant resources

- [Poe Discord](https://discord.com/invite/joinpoe)
202 changes: 202 additions & 0 deletions cli/src/config/mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import type { CLIConfig, ProviderConfig } from "./types.js"
import type { ExtensionState, ProviderSettings, ProviderSettingsEntry } from "../types/messages.js"
import { logs } from "../services/logs.js"
import { DEFAULT_MAX_CONCURRENT_FILE_READS } from "@kilocode/core-schemas"

export function mapConfigToExtensionState(
config: CLIConfig,
currentState?: Partial<ExtensionState>,
): Partial<ExtensionState> {
try {
// Find selected provider
let provider = config.providers.find((p) => p.id === config.provider)

if (!provider) {
logs.warn("Selected provider not found, using first provider", "ConfigMapper")
provider = config.providers[0]
if (!provider) {
throw new Error("No providers configured")
}
}

// Map provider config to API configuration
const apiConfiguration = mapProviderToApiConfig(provider)

// Create list of provider metadata
const listApiConfigMeta: ProviderSettingsEntry[] = config.providers.map((p) => ({
id: p.id,
name: p.id,
apiProvider: p.provider,
modelId: getModelIdForProvider(p),
}))

// Map auto-approval settings from CLI config to extension state
// These settings control whether the extension auto-approves operations
// or asks the CLI for approval (which then prompts the user)
const autoApproval = config.autoApproval
const autoApprovalEnabled = autoApproval?.enabled ?? false

return {
...currentState,
apiConfiguration,
currentApiConfigName: provider.id,
listApiConfigMeta,
telemetrySetting: config.telemetry ? "enabled" : "disabled",
mode: config.mode,
// Auto-approval settings - these control whether the extension auto-approves
// or defers to the CLI's approval flow
autoApprovalEnabled,
alwaysAllowReadOnly: autoApprovalEnabled && (autoApproval?.read?.enabled ?? false),
alwaysAllowReadOnlyOutsideWorkspace:
autoApprovalEnabled && (autoApproval?.read?.enabled ?? false) && (autoApproval?.read?.outside ?? false),
alwaysAllowWrite: autoApprovalEnabled && (autoApproval?.write?.enabled ?? false),
alwaysAllowWriteOutsideWorkspace:
autoApprovalEnabled &&
(autoApproval?.write?.enabled ?? false) &&
(autoApproval?.write?.outside ?? false),
alwaysAllowWriteProtected:
autoApprovalEnabled &&
(autoApproval?.write?.enabled ?? false) &&
(autoApproval?.write?.protected ?? false),
alwaysAllowBrowser: autoApprovalEnabled && (autoApproval?.browser?.enabled ?? false),
alwaysApproveResubmit: autoApprovalEnabled && (autoApproval?.retry?.enabled ?? false),
requestDelaySeconds: autoApproval?.retry?.delay ?? 10,
alwaysAllowMcp: autoApprovalEnabled && (autoApproval?.mcp?.enabled ?? false),
alwaysAllowModeSwitch: autoApprovalEnabled && (autoApproval?.mode?.enabled ?? false),
alwaysAllowSubtasks: autoApprovalEnabled && (autoApproval?.subtasks?.enabled ?? false),
alwaysAllowExecute: autoApprovalEnabled && (autoApproval?.execute?.enabled ?? false),
allowedCommands: autoApproval?.execute?.allowed ?? [],
deniedCommands: autoApproval?.execute?.denied ?? [],
alwaysAllowFollowupQuestions: autoApprovalEnabled && (autoApproval?.question?.enabled ?? false),
followupAutoApproveTimeoutMs: (autoApproval?.question?.timeout ?? 60) * 1000,
alwaysAllowUpdateTodoList: autoApprovalEnabled && (autoApproval?.todo?.enabled ?? false),
// Context management settings
maxConcurrentFileReads: config.maxConcurrentFileReads ?? DEFAULT_MAX_CONCURRENT_FILE_READS,
}
} catch (error) {
logs.error("Failed to map config to extension state", "ConfigMapper", { error })
throw error
}
}

export function mapProviderToApiConfig(provider: ProviderConfig): ProviderSettings {
const config: ProviderSettings = {
apiProvider: provider.provider,
}

// Copy all provider-specific fields
Object.keys(provider).forEach((key) => {
if (key !== "id" && key !== "provider") {
// Type assertion needed because we're dynamically accessing keys
;(config as Record<string, unknown>)[key] = (provider as Record<string, unknown>)[key]
}
})

return config
}

export function getModelIdForProvider(provider: ProviderConfig): string {
switch (provider.provider) {
case "kilocode":
return provider.kilocodeModel || ""
case "anthropic":
return provider.apiModelId || ""
case "openai-native":
return provider.apiModelId || ""
case "openrouter":
return provider.openRouterModelId || ""
case "ollama":
return provider.ollamaModelId || ""
case "lmstudio":
return provider.lmStudioModelId || ""
case "openai":
return provider.openAiModelId || ""
case "glama":
return provider.glamaModelId || ""
case "litellm":
return provider.litellmModelId || ""
case "deepinfra":
return provider.deepInfraModelId || ""
case "unbound":
return provider.unboundModelId || ""
case "requesty":
return provider.requestyModelId || ""
case "vercel-ai-gateway":
return provider.vercelAiGatewayModelId || ""
case "io-intelligence":
return provider.ioIntelligenceModelId || ""
case "ovhcloud":
return provider.ovhCloudAiEndpointsModelId || ""
case "poe":
return provider.poeModelId || ""
case "inception":
return provider.inceptionLabsModelId || ""
case "bedrock":
case "vertex":
case "gemini":
case "gemini-cli":
case "mistral":
case "moonshot":
case "minimax":
case "deepseek":
case "doubao":
case "qwen-code":
case "xai":
case "groq":
case "chutes":
case "cerebras":
case "sambanova":
case "zai":
case "fireworks":
case "featherless":
case "roo":
case "claude-code":
case "synthetic":
return provider.apiModelId || ""
case "virtual-quota-fallback":
return provider.profiles && provider.profiles.length > 0 ? `${provider.profiles.length} profile(s)` : ""
case "vscode-lm":
if (provider.vsCodeLmModelSelector) {
return `${provider.vsCodeLmModelSelector.vendor}/${provider.vsCodeLmModelSelector.family}`
}
return ""
case "huggingface":
return provider.huggingFaceModelId || ""
case "fake-ai":
case "human-relay":
return ""
}
}

export function mapExtensionStateToConfig(state: ExtensionState, currentConfig?: CLIConfig): CLIConfig {
// This is for future bi-directional sync if needed
const config: CLIConfig = currentConfig || {
version: "1.0.0",
mode: state.mode || "code",
telemetry: state.telemetrySetting === "enabled",
provider: state.currentApiConfigName || "default",
providers: [],
}

// Map current API configuration to provider
if (state.apiConfiguration) {
const providerId = state.currentApiConfigName || "current"
const existingProvider = config.providers.find((p) => p.id === providerId)

if (!existingProvider) {
const newProvider = {
id: providerId,
provider: state.apiConfiguration.apiProvider || "kilocode",
...state.apiConfiguration,
} as ProviderConfig
config.providers.push(newProvider)
} else {
// Update existing provider
Object.assign(existingProvider, state.apiConfiguration)
}

config.provider = providerId
}

return config
}
72 changes: 72 additions & 0 deletions cli/src/constants/providers/labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { ProviderName } from "../../types/messages.js"

/**
* Provider display labels mapping
* Maps provider internal names to user-friendly display names
*/
export const PROVIDER_LABELS: Record<ProviderName, string> = {
kilocode: "Kilo Code",
anthropic: "Anthropic",
"openai-native": "OpenAI",
"openai-codex": "OpenAI - ChatGPT Plus/Pro",
openrouter: "OpenRouter",
bedrock: "Amazon Bedrock",
gemini: "Google Gemini",
vertex: "GCP Vertex AI",
"claude-code": "Claude Code",
mistral: "Mistral",
groq: "Groq",
deepseek: "DeepSeek",
xai: "xAI (Grok)",
cerebras: "Cerebras",
ollama: "Ollama",
lmstudio: "LM Studio",
"vscode-lm": "VS Code LM API",
openai: "OpenAI Compatible",
glama: "Glama",
"nano-gpt": "Nano-GPT",
huggingface: "Hugging Face",
litellm: "LiteLLM",
moonshot: "Moonshot",
doubao: "Doubao",
chutes: "Chutes AI",
sambanova: "SambaNova",
fireworks: "Fireworks",
featherless: "Featherless",
deepinfra: "DeepInfra",
"io-intelligence": "IO Intelligence",
"qwen-code": "Qwen Code",
"gemini-cli": "Gemini CLI",
zai: "Zai",
minimax: "MiniMax",
unbound: "Unbound",
requesty: "Requesty",
roo: "Roo",
"vercel-ai-gateway": "Vercel AI Gateway",
"virtual-quota-fallback": "Virtual Quota Fallback",
"human-relay": "Human Relay",
"fake-ai": "Fake AI",
ovhcloud: "OVHcloud AI Endpoints",
poe: "Poe",
inception: "Inception",
synthetic: "Synthetic",
"sap-ai-core": "SAP AI Core",
baseten: "BaseTen",
}

/**
* Provider list with value and label pairs
* Used for selection components and dropdowns
*/
export const PROVIDER_OPTIONS: Array<{ value: ProviderName; label: string }> = Object.entries(PROVIDER_LABELS).map(
([value, label]) => ({ value: value as ProviderName, label }),
)

/**
* Get provider display label by provider name
* @param provider - Provider name or undefined
* @returns User-friendly display name
*/
export const getProviderLabel = (provider: ProviderName | undefined): string => {
return provider ? PROVIDER_LABELS[provider] || provider : "No provider selected"
}
Loading