From f5456d56cc0690d196c4bbfaf7f25b376428bcdf Mon Sep 17 00:00:00 2001 From: "kilo-maintainer[bot]" <260744684+kilo-maintainer[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:51:32 +0000 Subject: [PATCH 1/3] chore(jetbrains): bump CLI pin to v7.4.9 --- packages/kilo-jetbrains/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-jetbrains/package.json b/packages/kilo-jetbrains/package.json index 38402890d8..8214859179 100644 --- a/packages/kilo-jetbrains/package.json +++ b/packages/kilo-jetbrains/package.json @@ -8,7 +8,7 @@ "test": "./gradlew test", "test:ci": "bun script/test-ci.ts" }, - "version": "7.4.5", + "version": "7.4.9", "dependencies": {}, "devDependencies": {}, "peerDependencies": {} From f6ec8b87f6688b23b4caa02dd6e976f4b54635ea Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 16 Jul 2026 10:07:10 -0400 Subject: [PATCH 2/3] fix(jetbrains): handle upstream OpenAPI changes --- bun.lock | 2 +- .../backend/app/KiloBackendSessionManager.kt | 40 ++++++++++++----- .../normalization/OpenApiSpecNormalizer.kt | 44 +++++++++++++++---- .../OpenApiSpecNormalizerTest.kt | 11 +++++ 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/bun.lock b/bun.lock index 3ec0011a36..26d64016d7 100644 --- a/bun.lock +++ b/bun.lock @@ -294,7 +294,7 @@ }, "packages/kilo-jetbrains": { "name": "@kilocode/kilo-jetbrains", - "version": "7.4.5", + "version": "7.4.9", }, "packages/kilo-memory": { "name": "@kilocode/kilo-memory", diff --git a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendSessionManager.kt b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendSessionManager.kt index 949eb170cf..f4184e6d4b 100644 --- a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendSessionManager.kt +++ b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendSessionManager.kt @@ -278,9 +278,31 @@ class KiloBackendSessionManager( ), summary = s.summary?.let { SessionSummaryDto( - additions = it.additions.safeInt(), - deletions = it.deletions.safeInt(), - files = it.files.safeInt(), + additions = count(it.additions), + deletions = count(it.deletions), + files = count(it.files), + ) + }, + revert = revertDto(s.revert), + ) + + private fun dto(s: ai.kilocode.jetbrains.api.model.Session1) = SessionDto( + id = s.id, + projectID = s.projectID, + directory = s.directory, + parentID = s.parentID, + title = s.title, + version = s.version, + time = SessionTimeDto( + created = time(s.id, "created", s.time.created), + updated = time(s.id, "updated", s.time.updated), + archived = s.time.archived, + ), + summary = s.summary?.let { + SessionSummaryDto( + additions = count(it.additions), + deletions = count(it.deletions), + files = count(it.files), ) }, revert = revertDto(s.revert), @@ -300,9 +322,9 @@ class KiloBackendSessionManager( ), summary = s.summary?.let { SessionSummaryDto( - additions = it.additions?.safeInt() ?: 0, - deletions = it.deletions?.safeInt() ?: 0, - files = it.files?.safeInt() ?: 0, + additions = count(it.additions), + deletions = count(it.deletions), + files = count(it.files), ) }, revert = revertDto(s.revert), @@ -312,10 +334,6 @@ class KiloBackendSessionManager( revertDto(it.messageID, it.partID, it.snapshot, it.diff) } - private fun revertDto(s: ai.kilocode.jetbrains.api.model.GlobalSessionRevert?) = s?.let { - revertDto(it.messageID, it.partID, it.snapshot, it.diff) - } - private fun revertDto(message: String, part: String?, snapshot: String?, diff: String?) = SessionRevertDto( messageID = message, @@ -334,6 +352,8 @@ class KiloBackendSessionManager( private fun encode(value: String) = java.net.URLEncoder.encode(value, Charsets.UTF_8) + private fun count(value: Double?) = value?.safeInt() ?: 0 + private fun time(id: String, field: String, value: Number?): Double { if (value != null) return value.toDouble() log.warn("Session $id missing $field timestamp; defaulting to 0.0") diff --git a/packages/kilo-jetbrains/build-tasks/src/main/kotlin/normalization/OpenApiSpecNormalizer.kt b/packages/kilo-jetbrains/build-tasks/src/main/kotlin/normalization/OpenApiSpecNormalizer.kt index 09c3f8cb3f..c3af05ab4a 100644 --- a/packages/kilo-jetbrains/build-tasks/src/main/kotlin/normalization/OpenApiSpecNormalizer.kt +++ b/packages/kilo-jetbrains/build-tasks/src/main/kotlin/normalization/OpenApiSpecNormalizer.kt @@ -124,16 +124,19 @@ internal object OpenApiSpecNormalizer { as? JsonObject ?: return root val props = schema["properties"] as? JsonObject ?: return root - val nullable = setOf("balance", "kiloPass", "currentOrgId") + val keys = setOf("balance", "kiloPass", "currentOrgId") val fixed = JsonObject(props.mapValues { (key, value) -> - if (key !in nullable) return@mapValues value - val obj = value as? JsonObject ?: return@mapValues value - // Skip if already wrapped (has anyOf containing {type:null}). - val existing = obj["anyOf"] as? JsonArray - if (existing != null && existing.any { - (it as? JsonObject)?.get("type")?.let { t -> (t as? JsonPrimitive)?.content } == "null" - }) return@mapValues value - JsonObject(mapOf("anyOf" to JsonArray(listOf(obj, JsonObject(mapOf("type" to JsonPrimitive("null"))))))) + if (key !in keys) return@mapValues value + val wrapped = nullable(value) + when (key) { + "balance" -> nullableProps(wrapped, setOf("balance")) + "kiloPass" -> nullableProps(wrapped, setOf( + "currentPeriodBaseCreditsUsd", + "currentPeriodUsageUsd", + "currentPeriodBonusCreditsUsd", + )) + else -> wrapped + } }) // Rebuild nested objects up to root. @@ -152,6 +155,29 @@ internal object OpenApiSpecNormalizer { return JsonObject(root + mapOf("paths" to newPaths)) } + private fun nullable(value: JsonElement): JsonElement { + val obj = value as? JsonObject ?: return value + val any = obj["anyOf"] as? JsonArray + if (any != null && any.any(::nullSchema)) return obj + return JsonObject(mapOf("anyOf" to JsonArray(listOf(obj, JsonObject(mapOf("type" to JsonPrimitive("null"))))))) + } + + private fun nullableProps(value: JsonElement, names: Set): JsonElement { + val obj = value as? JsonObject ?: return value + val any = obj["anyOf"] as? JsonArray + if (any != null) { + return JsonObject(obj + mapOf("anyOf" to JsonArray(any.map { nullableProps(it, names) }))) + } + val props = obj["properties"] as? JsonObject ?: return obj + val fixed = JsonObject(props.mapValues { (key, prop) -> + if (key in names) nullable(prop) else prop + }) + return JsonObject(obj + mapOf("properties" to fixed)) + } + + private fun nullSchema(value: JsonElement) = + (value as? JsonObject)?.get("type")?.let { (it as? JsonPrimitive)?.content } == "null" + /** * Deduplicate the root-level "tags" array by name — the spec validator * rejects repeated tag names even when they describe different things. diff --git a/packages/kilo-jetbrains/build-tasks/src/test/kotlin/normalization/OpenApiSpecNormalizerTest.kt b/packages/kilo-jetbrains/build-tasks/src/test/kotlin/normalization/OpenApiSpecNormalizerTest.kt index 492ea3c0a6..b9b9fb1398 100644 --- a/packages/kilo-jetbrains/build-tasks/src/test/kotlin/normalization/OpenApiSpecNormalizerTest.kt +++ b/packages/kilo-jetbrains/build-tasks/src/test/kotlin/normalization/OpenApiSpecNormalizerTest.kt @@ -148,6 +148,12 @@ class OpenApiSpecNormalizerTest { val balanceTypes = balanceAnyOf.map { (it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content } } assert("null" in balanceTypes) { "balance anyOf should include null but got $balanceTypes" } assert(balanceAnyOf.any { it is JsonObject && "properties" in it }) { "balance anyOf should include the object schema" } + val balanceObject = balanceAnyOf.filterIsInstance().first { "properties" in it } + val balanceValue = obj(obj(balanceObject["properties"])["balance"]) + val balanceValueTypes = arr(balanceValue["anyOf"]).map { + (it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content } + } + assert("null" in balanceValueTypes) { "inner balance value should include null but got $balanceValueTypes" } // kiloPass must be anyOf [object, null] val pass = obj(props["kiloPass"]) @@ -156,6 +162,11 @@ class OpenApiSpecNormalizerTest { val passTypes = passAnyOf.map { (it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content } } assert("null" in passTypes) { "kiloPass anyOf should include null but got $passTypes" } assert(passAnyOf.any { it is JsonObject && "properties" in it }) { "kiloPass anyOf should include the object schema" } + val passObject = passAnyOf.filterIsInstance().first { "properties" in it } + val passProps = obj(passObject["properties"]) + val base = obj(passProps["currentPeriodBaseCreditsUsd"]) + val baseTypes = arr(base["anyOf"]).map { (it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content } } + assert("null" in baseTypes) { "inner kiloPass value should include null but got $baseTypes" } // currentOrgId must be anyOf [string, null] val orgId = obj(props["currentOrgId"]) From 1b1a0b655c1123334e7aee41f3362cf28f11f963 Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 16 Jul 2026 10:45:33 -0400 Subject: [PATCH 3/3] refactor(jetbrains): dedupe session dto mapping --- .../backend/app/KiloBackendSessionManager.kt | 106 ++++++++++-------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendSessionManager.kt b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendSessionManager.kt index f4184e6d4b..f5b9fc4f71 100644 --- a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendSessionManager.kt +++ b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendSessionManager.kt @@ -264,72 +264,82 @@ class KiloBackendSessionManager( // ------ mapping (generated API model → DTO) ------ - private fun dto(s: ai.kilocode.jetbrains.api.model.Session) = SessionDto( + private fun dto(s: ai.kilocode.jetbrains.api.model.Session) = dto( id = s.id, - projectID = s.projectID, - directory = s.directory, - parentID = s.parentID, + project = s.projectID, + dir = s.directory, + parent = s.parentID, title = s.title, version = s.version, - time = SessionTimeDto( - created = time(s.id, "created", s.time.created), - updated = time(s.id, "updated", s.time.updated), - archived = s.time.archived, - ), - summary = s.summary?.let { - SessionSummaryDto( - additions = count(it.additions), - deletions = count(it.deletions), - files = count(it.files), - ) - }, + created = s.time.created, + updated = s.time.updated, + archived = s.time.archived, + summary = s.summary?.let { summary(it.additions, it.deletions, it.files) }, revert = revertDto(s.revert), ) - private fun dto(s: ai.kilocode.jetbrains.api.model.Session1) = SessionDto( + private fun dto(s: ai.kilocode.jetbrains.api.model.Session1) = dto( id = s.id, - projectID = s.projectID, - directory = s.directory, - parentID = s.parentID, + project = s.projectID, + dir = s.directory, + parent = s.parentID, title = s.title, version = s.version, - time = SessionTimeDto( - created = time(s.id, "created", s.time.created), - updated = time(s.id, "updated", s.time.updated), - archived = s.time.archived, - ), - summary = s.summary?.let { - SessionSummaryDto( - additions = count(it.additions), - deletions = count(it.deletions), - files = count(it.files), - ) - }, + created = s.time.created, + updated = s.time.updated, + archived = s.time.archived, + summary = s.summary?.let { summary(it.additions, it.deletions, it.files) }, revert = revertDto(s.revert), ) - private fun dto(s: GlobalSession) = SessionDto( + private fun dto(s: GlobalSession) = dto( id = s.id, - projectID = s.projectID, - directory = s.directory, - parentID = s.parentID, + project = s.projectID, + dir = s.directory, + parent = s.parentID, title = s.title, version = s.version, - time = SessionTimeDto( - created = time(s.id, "created", s.time.created), - updated = time(s.id, "updated", s.time.updated), - archived = s.time.archived, - ), - summary = s.summary?.let { - SessionSummaryDto( - additions = count(it.additions), - deletions = count(it.deletions), - files = count(it.files), - ) - }, + created = s.time.created, + updated = s.time.updated, + archived = s.time.archived, + summary = s.summary?.let { summary(it.additions, it.deletions, it.files) }, revert = revertDto(s.revert), ) + private fun dto( + id: String, + project: String, + dir: String, + parent: String?, + title: String, + version: String, + created: Number?, + updated: Number?, + archived: Double?, + summary: SessionSummaryDto?, + revert: SessionRevertDto?, + ) = SessionDto( + id = id, + projectID = project, + directory = dir, + parentID = parent, + title = title, + version = version, + time = SessionTimeDto( + created = time(id, "created", created), + updated = time(id, "updated", updated), + archived = archived, + ), + summary = summary, + revert = revert, + ) + + private fun summary(add: Double?, del: Double?, files: Double?) = SessionSummaryDto( + additions = count(add), + deletions = count(del), + files = count(files), + ) + private fun revertDto(s: ai.kilocode.jetbrains.api.model.SessionRevert?) = s?.let { revertDto(it.messageID, it.partID, it.snapshot, it.diff) }