feat(vscode): propagate isFree from cloud API through CLI to extension

Add isFree field to the full model data pipeline: gateway schema,
ModelsDev.Model, Provider.Model, OpenAPI spec, and SDK types.
Remove cost-based isFree fallback in the extension model selector —
rely solely on the backend-provided value.
This commit is contained in:
kirillk
2026-03-17 13:33:58 -04:00
parent 86e8143b77
commit 4e628a8cc0
6 changed files with 20 additions and 6 deletions
+2
View File
@@ -30,6 +30,7 @@ const openRouterModelSchema = z.object({
top_provider: z.object({ max_completion_tokens: z.number().nullish() }).optional(),
supported_parameters: z.array(z.string()).optional(),
preferredIndex: z.number().optional(),
isFree: z.boolean().optional(),
opencode: z
.object({
family: z.string().optional(),
@@ -162,6 +163,7 @@ function transformToModelDevFormat(model: OpenRouterModel): any {
variants: model.opencode?.variants,
prompt: model.opencode?.prompt,
tool_call: supportsTools,
isFree: model.isFree,
...(inputPrice !== undefined &&
outputPrice !== undefined && {
cost: {
@@ -10,12 +10,8 @@ export function providerSortKey(providerID: string, order = PROVIDER_ORDER): num
return idx >= 0 ? idx : order.length
}
export function isFree(model: Pick<EnrichedModel, "isFree" | "cost" | "providerID">): boolean {
if (model.isFree !== undefined) return model.isFree
// TODO: temporary — this logic will be moved to the backend
if (model.providerID !== KILO_GATEWAY_ID) return false
// TODO: fallback to cost check — delete once backend ships isFree attribute
return model.cost?.input === 0 && model.cost?.output === 0
export function isFree(model: Pick<EnrichedModel, "isFree">): boolean {
return model.isFree === true
}
export function stripSubProviderPrefix(name: string): string {
+1
View File
@@ -86,6 +86,7 @@ export namespace ModelsDev {
// kilocode_change start
recommendedIndex: z.number().optional(),
prompt: Prompt.optional().catch(undefined),
isFree: z.boolean().optional(),
// kilocode_change end
experimental: z.boolean().optional(),
@@ -689,6 +689,7 @@ export namespace Provider {
// kilocode_change start
recommendedIndex: z.number().optional(),
prompt: Prompt.optional().catch(undefined),
isFree: z.boolean().optional(),
// kilocode_change end
})
.meta({
@@ -775,6 +776,7 @@ export namespace Provider {
variants: provider.id === "kilo" ? (model.variants ?? {}) : {},
recommendedIndex: model.recommendedIndex,
prompt: model.prompt,
isFree: model.isFree,
// kilocode_change end
}
@@ -923,6 +925,7 @@ export namespace Provider {
// kilocode_change start
recommendedIndex: model.recommendedIndex ?? existingModel?.recommendedIndex,
prompt: model.prompt ?? existingModel?.prompt,
isFree: model.isFree ?? existingModel?.isFree,
// kilocode_change end
}
const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {})
+3
View File
@@ -1214,6 +1214,7 @@ export type ProviderConfig = {
}
recommendedIndex?: number
prompt?: "codex" | "gemini" | "beast" | "anthropic" | "trinity" | "anthropic_without_todo"
isFree?: boolean
experimental?: boolean
status?: "alpha" | "beta" | "deprecated"
options?: {
@@ -1646,6 +1647,7 @@ export type Model = {
}
recommendedIndex?: number
prompt?: "codex" | "gemini" | "beast" | "anthropic" | "trinity" | "anthropic_without_todo"
isFree?: boolean
}
export type Provider = {
@@ -4159,6 +4161,7 @@ export type ProviderListResponses = {
}
recommendedIndex?: number
prompt?: "codex" | "gemini" | "beast" | "anthropic" | "trinity" | "anthropic_without_todo"
isFree?: boolean
experimental?: boolean
status?: "alpha" | "beta" | "deprecated"
options: {
+9
View File
@@ -4838,6 +4838,9 @@
"type": "string",
"enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"]
},
"isFree": {
"type": "boolean"
},
"experimental": {
"type": "boolean"
},
@@ -11403,6 +11406,9 @@
"type": "string",
"enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"]
},
"isFree": {
"type": "boolean"
},
"experimental": {
"type": "boolean"
},
@@ -12373,6 +12379,9 @@
"prompt": {
"type": "string",
"enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo"]
},
"isFree": {
"type": "boolean"
}
},
"required": [