mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge pull request #7320 from Kilo-Org/catrielmuller/migrate-openai-codex-from-legacy
fix(vscode): migrate openai-codex, nano-gpt, poe, aihubmix, zenmux from legacy extension
This commit is contained in:
@@ -171,6 +171,24 @@ export interface LegacyProviderSettings {
|
||||
// Synthetic
|
||||
syntheticApiKey?: string
|
||||
|
||||
// NanoGPT
|
||||
nanoGptApiKey?: string
|
||||
nanoGptModelId?: string
|
||||
|
||||
// Poe
|
||||
poeApiKey?: string
|
||||
poeModelId?: string
|
||||
|
||||
// AiHubMix
|
||||
aihubmixApiKey?: string
|
||||
aihubmixModelId?: string
|
||||
aihubmixBaseUrl?: string
|
||||
|
||||
// ZenMux
|
||||
zenmuxApiKey?: string
|
||||
zenmuxModelId?: string
|
||||
zenmuxBaseUrl?: string
|
||||
|
||||
// Allow dynamic property access for provider-mapping lookups
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
PermissionObjectConfig,
|
||||
} from "@kilocode/sdk/v2/client"
|
||||
import { PROVIDER_MAP, UNSUPPORTED_PROVIDERS, DEFAULT_MODE_SLUGS } from "./provider-mapping"
|
||||
import type { ProviderMapping } from "./provider-mapping"
|
||||
import type {
|
||||
LegacyProviderProfiles,
|
||||
LegacyProviderSettings,
|
||||
@@ -37,6 +38,7 @@ import type {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SECRET_KEY = "roo_cline_config_api_config"
|
||||
const CODEX_OAUTH_SECRET_KEY = "openai-codex-oauth-credentials"
|
||||
const MIGRATION_STATUS_KEY = "kilo.legacyMigrationStatus"
|
||||
|
||||
type MigrationStatus = "completed" | "skipped"
|
||||
@@ -67,10 +69,14 @@ export async function detectLegacyData(context: vscode.ExtensionContext): Promis
|
||||
const customModes = await readLegacyCustomModes(context)
|
||||
const settings = readLegacySettings(context)
|
||||
|
||||
const providers = buildProviderList(profiles)
|
||||
const oauthProviders = new Set<string>()
|
||||
const codexRaw = await context.secrets.get(CODEX_OAUTH_SECRET_KEY)
|
||||
if (codexRaw) oauthProviders.add("openai-codex")
|
||||
|
||||
const providers = buildProviderList(profiles, oauthProviders)
|
||||
const mcpServers = buildMcpServerList(mcpSettings)
|
||||
const modes = buildCustomModeList(customModes)
|
||||
const defaultModel = resolveDefaultModel(profiles)
|
||||
const defaultModel = resolveDefaultModel(profiles, oauthProviders)
|
||||
|
||||
const hasSettings =
|
||||
settings.autoApprovalEnabled !== undefined ||
|
||||
@@ -137,7 +143,7 @@ export async function migrate(
|
||||
continue
|
||||
}
|
||||
onProgress(profileName, "migrating")
|
||||
const result = await migrateProvider(profileName, settings, client)
|
||||
const result = await migrateProvider(context, profileName, settings, client)
|
||||
results.push(result)
|
||||
onProgress(profileName, result.status, result.message)
|
||||
}
|
||||
@@ -245,6 +251,7 @@ export async function migrate(
|
||||
*/
|
||||
export async function clearLegacyData(context: vscode.ExtensionContext): Promise<void> {
|
||||
await context.secrets.delete(SECRET_KEY)
|
||||
await context.secrets.delete(CODEX_OAUTH_SECRET_KEY)
|
||||
|
||||
const legacyStateKeys = [
|
||||
"kilo-code.allowedCommands",
|
||||
@@ -308,6 +315,7 @@ export async function clearLegacyData(context: vscode.ExtensionContext): Promise
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function migrateProvider(
|
||||
context: vscode.ExtensionContext,
|
||||
profileName: string,
|
||||
settings: LegacyProviderSettings,
|
||||
client: KiloClient,
|
||||
@@ -336,6 +344,31 @@ async function migrateProvider(
|
||||
}
|
||||
}
|
||||
|
||||
// OAuth providers store credentials in a separate VS Code secret
|
||||
if (mapping.oauthSecretKey) {
|
||||
const creds = await readOAuthCredentials(context, mapping.oauthSecretKey)
|
||||
if (!creds) {
|
||||
return { item: profileName, category: "provider", status: "warning", message: "No OAuth credentials found" }
|
||||
}
|
||||
await client.auth.set({ providerID: mapping.id, auth: { type: "oauth" as const, ...creds } })
|
||||
return { item: profileName, category: "provider", status: "success" }
|
||||
}
|
||||
|
||||
// Providers that use env/ADC-based auth (e.g. Vertex AI) — skip auth.set, only migrate config options
|
||||
if (mapping.skipAuth) {
|
||||
await migrateConfigFields(mapping, settings, client)
|
||||
// Warn users who had inline service account credentials — the CLI uses ADC only
|
||||
const hadCredentials = Boolean(settings.vertexJsonCredentials ?? settings.vertexKeyFile)
|
||||
return {
|
||||
item: profileName,
|
||||
category: "provider",
|
||||
status: hadCredentials ? "warning" : "success",
|
||||
message: hadCredentials
|
||||
? "Project and location migrated. The new CLI uses Application Default Credentials — set GOOGLE_APPLICATION_CREDENTIALS or run 'gcloud auth application-default login'"
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
const apiKey = settings[mapping.key] as string | undefined
|
||||
if (!apiKey) {
|
||||
return { item: profileName, category: "provider", status: "warning", message: "No API key found in profile" }
|
||||
@@ -363,9 +396,29 @@ async function migrateProvider(
|
||||
}
|
||||
}
|
||||
|
||||
await migrateConfigFields(mapping, settings, client)
|
||||
|
||||
return { item: profileName, category: "provider", status: "success" }
|
||||
}
|
||||
|
||||
async function migrateConfigFields(
|
||||
mapping: ProviderMapping,
|
||||
settings: LegacyProviderSettings,
|
||||
client: KiloClient,
|
||||
): Promise<void> {
|
||||
if (!mapping.configFields?.length) return
|
||||
const opts: Record<string, string> = {}
|
||||
for (const { from, option } of mapping.configFields) {
|
||||
const val = settings[from] as string | undefined
|
||||
if (val) opts[option] = val
|
||||
}
|
||||
if (Object.keys(opts).length > 0) {
|
||||
await client.global.config.update({
|
||||
config: { provider: { [mapping.id]: { options: opts } } },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateDefaultModel(settings: LegacyProviderSettings, client: KiloClient): Promise<MigrationResultItem> {
|
||||
const provider = settings.apiProvider
|
||||
if (!provider) {
|
||||
@@ -699,6 +752,35 @@ function convertCustomMode(mode: LegacyCustomMode): AgentConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal — OAuth credential helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reads OAuth credentials stored in a separate VS Code secret (e.g. openai-codex-oauth-credentials).
|
||||
* Returns the fields needed by the CLI's Auth.Oauth type, or null if absent/malformed.
|
||||
*/
|
||||
async function readOAuthCredentials(
|
||||
context: vscode.ExtensionContext,
|
||||
secretKey: string,
|
||||
): Promise<{ access: string; refresh: string; expires: number; accountId?: string } | null> {
|
||||
const raw = await context.secrets.get(secretKey)
|
||||
if (!raw) return null
|
||||
const parsed = (() => {
|
||||
try {
|
||||
return JSON.parse(raw) as Record<string, unknown>
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})()
|
||||
if (!parsed) return null
|
||||
const access = parsed.access_token as string | undefined
|
||||
const refresh = parsed.refresh_token as string | undefined
|
||||
const expires = parsed.expires as number | undefined
|
||||
if (!access || !refresh || expires === undefined) return null
|
||||
return { access, refresh, expires, accountId: parsed.accountId as string | undefined }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal — reading legacy data from storage
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -921,7 +1003,10 @@ function parseCustomModesYaml(text: string): LegacyCustomMode[] | null {
|
||||
// Internal — building display lists for the wizard
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildProviderList(profiles: LegacyProviderProfiles | null): MigrationProviderInfo[] {
|
||||
function buildProviderList(
|
||||
profiles: LegacyProviderProfiles | null,
|
||||
oauthProviders: Set<string>,
|
||||
): MigrationProviderInfo[] {
|
||||
if (!profiles?.apiConfigs) return []
|
||||
|
||||
return Object.entries(profiles.apiConfigs).map(([profileName, settings]) => {
|
||||
@@ -932,7 +1017,13 @@ function buildProviderList(profiles: LegacyProviderProfiles | null): MigrationPr
|
||||
const modelField = mapping?.modelField ?? "apiModelId"
|
||||
const model = settings[modelField] as string | undefined
|
||||
|
||||
const hasApiKey = mapping ? Boolean(settings[mapping.key]) : false
|
||||
const hasApiKey = mapping?.oauthSecretKey
|
||||
? oauthProviders.has(provider)
|
||||
: mapping?.skipAuth
|
||||
? (mapping.configFields?.some((f) => Boolean(settings[f.from])) ?? false)
|
||||
: mapping
|
||||
? Boolean(settings[mapping.key])
|
||||
: false
|
||||
|
||||
return {
|
||||
profileName,
|
||||
@@ -957,12 +1048,18 @@ function buildCustomModeList(modes: LegacyCustomMode[] | null): MigrationCustomM
|
||||
return modes.filter((m) => !DEFAULT_MODE_SLUGS.has(m.slug)).map((m) => ({ name: m.name, slug: m.slug }))
|
||||
}
|
||||
|
||||
function resolveDefaultModel(profiles: LegacyProviderProfiles | null): { provider: string; model: string } | undefined {
|
||||
function resolveDefaultModel(
|
||||
profiles: LegacyProviderProfiles | null,
|
||||
oauthProviders: Set<string>,
|
||||
): { provider: string; model: string } | undefined {
|
||||
if (!profiles?.currentApiConfigName) return undefined
|
||||
const active = profiles.apiConfigs[profiles.currentApiConfigName]
|
||||
if (!active?.apiProvider) return undefined
|
||||
const mapping = PROVIDER_MAP[active.apiProvider]
|
||||
if (!mapping) return undefined
|
||||
// If the active profile requires OAuth credentials (e.g. openai-codex) but they are
|
||||
// unavailable, do not offer default-model migration — it would write a broken reference.
|
||||
if (mapping.oauthSecretKey && !oauthProviders.has(active.apiProvider)) return undefined
|
||||
const modelField = mapping.modelField ?? "apiModelId"
|
||||
const model = active[modelField] as string | undefined
|
||||
if (!model) return undefined
|
||||
|
||||
@@ -18,6 +18,12 @@ export interface ProviderMapping {
|
||||
urlField?: string
|
||||
/** Field holding an organization/account ID (used for OAuth-style auth) */
|
||||
organizationIdField?: string
|
||||
/** VS Code secret key holding OAuth credentials stored separately from the provider profile */
|
||||
oauthSecretKey?: string
|
||||
/** If true, skip auth.set entirely — provider uses env/ADC-based auth (e.g. Vertex AI) */
|
||||
skipAuth?: boolean
|
||||
/** Legacy settings fields to write as provider config options (e.g. project/location for Vertex) */
|
||||
configFields?: Array<{ from: string; option: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,6 +71,11 @@ export const PROVIDER_MAP: Record<string, ProviderMapping> = {
|
||||
id: "google-vertex",
|
||||
key: "vertexJsonCredentials",
|
||||
name: "Google Vertex AI",
|
||||
skipAuth: true,
|
||||
configFields: [
|
||||
{ from: "vertexProjectId", option: "project" },
|
||||
{ from: "vertexRegion", option: "location" },
|
||||
],
|
||||
},
|
||||
bedrock: {
|
||||
id: "amazon-bedrock",
|
||||
@@ -240,6 +251,38 @@ export const PROVIDER_MAP: Record<string, ProviderMapping> = {
|
||||
key: "syntheticApiKey",
|
||||
name: "Synthetic",
|
||||
},
|
||||
"openai-codex": {
|
||||
id: "openai",
|
||||
key: "",
|
||||
name: "OpenAI (ChatGPT Plus/Pro)",
|
||||
oauthSecretKey: "openai-codex-oauth-credentials",
|
||||
},
|
||||
"nano-gpt": {
|
||||
id: "nano-gpt",
|
||||
key: "nanoGptApiKey",
|
||||
name: "NanoGPT",
|
||||
modelField: "nanoGptModelId",
|
||||
},
|
||||
poe: {
|
||||
id: "poe",
|
||||
key: "poeApiKey",
|
||||
name: "Poe",
|
||||
modelField: "poeModelId",
|
||||
},
|
||||
aihubmix: {
|
||||
id: "aihubmix",
|
||||
key: "aihubmixApiKey",
|
||||
name: "AiHubMix",
|
||||
modelField: "aihubmixModelId",
|
||||
urlField: "aihubmixBaseUrl",
|
||||
},
|
||||
zenmux: {
|
||||
id: "zenmux",
|
||||
key: "zenmuxApiKey",
|
||||
name: "ZenMux",
|
||||
modelField: "zenmuxModelId",
|
||||
urlField: "zenmuxBaseUrl",
|
||||
},
|
||||
}
|
||||
|
||||
/** Providers that have no equivalent in the new CLI backend */
|
||||
@@ -248,16 +291,11 @@ export const UNSUPPORTED_PROVIDERS = new Set([
|
||||
"human-relay",
|
||||
"vscode-lm",
|
||||
"claude-code",
|
||||
"openai-codex",
|
||||
"qwen-code",
|
||||
"virtual-quota-fallback",
|
||||
"glama",
|
||||
"roo",
|
||||
"nano-gpt",
|
||||
"poe",
|
||||
"aihubmix",
|
||||
"apertis",
|
||||
"zenmux",
|
||||
])
|
||||
|
||||
/** Built-in default mode slugs that should not be migrated */
|
||||
|
||||
Reference in New Issue
Block a user