feat: migrate openai codex plan from legacy extension

This commit is contained in:
Catriel Müller
2026-03-19 11:59:29 -03:00
parent 6ed7fdb40d
commit 00d407fea2
3 changed files with 109 additions and 9 deletions
@@ -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
}
@@ -37,6 +37,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,7 +68,11 @@ 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)
@@ -137,7 +142,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 +250,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 +314,7 @@ export async function clearLegacyData(context: vscode.ExtensionContext): Promise
// ---------------------------------------------------------------------------
async function migrateProvider(
context: vscode.ExtensionContext,
profileName: string,
settings: LegacyProviderSettings,
client: KiloClient,
@@ -336,6 +343,16 @@ 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" }
}
const apiKey = settings[mapping.key] as string | undefined
if (!apiKey) {
return { item: profileName, category: "provider", status: "warning", message: "No API key found in profile" }
@@ -699,6 +716,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 +967,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 +981,11 @@ 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
? Boolean(settings[mapping.key])
: false
return {
profileName,
@@ -18,6 +18,8 @@ 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
}
/**
@@ -240,6 +242,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 +282,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 */