Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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-glm-4-7-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Add GLM-4.7 model support to Z.ai provider
1 change: 1 addition & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"monaco-vscode-textmate-theme-converter": "^0.1.7",
"node-cache": "^5.1.2",
"node-ipc": "npm:[email protected]",
"node-machine-id": "^1.1.12",
"ollama": "^0.5.17",
"openai": "^5.12.2",
"os-name": "^6.0.0",
Expand Down
3 changes: 2 additions & 1 deletion cli/src/host/VSCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as path from "path"
import { logs } from "../services/logs.js"
import { KiloCodePaths } from "../utils/paths.js"
import { Package } from "../constants/package.js"
import { machineIdSync } from "node-machine-id"

// Identity information for VSCode environment
export interface IdentityInfo {
Expand Down Expand Up @@ -2321,7 +2322,7 @@ export function createVSCodeAPIMock(extensionRootPath: string, workspacePath: st
appName: `wrapper|cli|cli|${Package.version}`,
appRoot: import.meta.dirname,
language: "en",
machineId: identity?.machineId || "cli-machine-id",
machineId: identity?.machineId || machineIdSync(),
sessionId: identity?.sessionId || "cli-session-id",
remoteName: undefined,
shell: process.env.SHELL || "/bin/bash",
Expand Down
11 changes: 2 additions & 9 deletions cli/src/services/telemetry/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import * as fs from "fs-extra"
import * as path from "path"
import * as crypto from "crypto"
import * as os from "os"
import { KiloCodePaths } from "../../utils/paths.js"
import { logs } from "../logs.js"
import { getAppUrl } from "@roo-code/types"
import { machineIdSync } from "node-machine-id"

/**
* User identity structure
Expand Down Expand Up @@ -219,14 +219,7 @@ export class IdentityManager {
*/
private getMachineId(): string {
try {
// Use hostname + platform + architecture as machine identifier
const hostname = os.hostname()
const platform = os.platform()
const arch = os.arch()
const combined = `${hostname}-${platform}-${arch}`

// Hash the combined string for privacy
return crypto.createHash("sha256").update(combined).digest("hex").substring(0, 16)
return machineIdSync()
} catch (error) {
logs.warn("Failed to get machine ID", "IdentityManager", { error })
return "unknown"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ai.kilocode.jetbrains.editor.EditorAndDocManager
import ai.kilocode.jetbrains.ipc.NodeSocket
import ai.kilocode.jetbrains.ipc.PersistentProtocol
import ai.kilocode.jetbrains.ipc.proxy.ResponsiveState
import ai.kilocode.jetbrains.util.MachineIdUtil
import ai.kilocode.jetbrains.util.PluginConstants
import ai.kilocode.jetbrains.util.PluginResourceUtil
import ai.kilocode.jetbrains.util.URI
Expand Down Expand Up @@ -269,7 +270,7 @@ class ExtensionHostManager : Disposable {
),
"telemetryInfo" to mapOf(
"sessionId" to "intellij-session",
"machineId" to "intellij-machine",
"machineId" to MachineIdUtil.getMachineId(),
"sqmId" to "",
"devDeviceId" to "",
"firstSessionDate" to java.time.Instant.now().toString(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package ai.kilocode.jetbrains.util

import com.intellij.openapi.diagnostic.Logger
import java.io.File
import java.security.MessageDigest

/**
* Utility class for generating a unique machine identifier.
* This implementation mirrors the behavior of the node-machine-id npm package
* to ensure consistency across CLI and JetBrains plugin.
*
* The machine ID is generated from platform-specific sources:
* - Windows: MachineGuid from the Windows registry
* - macOS: IOPlatformUUID from IOKit
* - Linux: /var/lib/dbus/machine-id or /etc/machine-id
*
* The raw ID is hashed using SHA-256 to match the format produced by node-machine-id.
*/
object MachineIdUtil {
private val LOG = Logger.getInstance(MachineIdUtil::class.java)
private var cachedMachineId: String? = null

/**
* Get the machine ID, generating it if not already cached.
* Returns a SHA-256 hash of the platform-specific machine identifier.
*/
fun getMachineId(): String {
cachedMachineId?.let { return it }

val rawId = getRawMachineId()
val hashedId = hashMachineId(rawId)
cachedMachineId = hashedId
LOG.info("Generated machine ID: ${hashedId.take(8)}...")
return hashedId
}

/**
* Get the raw (unhashed) machine ID for the current platform.
*/
private fun getRawMachineId(): String {
return when {
isWindows() -> getWindowsMachineId()
isMacOS() -> getMacOSMachineId()
isLinux() -> getLinuxMachineId()
else -> getFallbackMachineId()
}
}

private fun isWindows(): Boolean =
System.getProperty("os.name").lowercase().contains("windows")

private fun isMacOS(): Boolean =
System.getProperty("os.name").lowercase().contains("mac")

private fun isLinux(): Boolean =
System.getProperty("os.name").lowercase().contains("linux")

/**
* Get machine ID on Windows by reading the MachineGuid from the registry.
*/
private fun getWindowsMachineId(): String {
return try {
val process = ProcessBuilder(
"reg", "query",
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography",
"/v", "MachineGuid"
).start()

val output = process.inputStream.bufferedReader().readText()
val exitCode = process.waitFor()

if (exitCode != 0) {
LOG.warn("Registry query failed with exit code: $exitCode")
return getFallbackMachineId()
}

// Parse the registry output to extract the GUID
// Output format: " MachineGuid REG_SZ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
val regex = Regex("MachineGuid\\s+REG_SZ\\s+([\\w-]+)")
val match = regex.find(output)

if (match != null) {
match.groupValues[1]
} else {
LOG.warn("Could not parse MachineGuid from registry output")
getFallbackMachineId()
}
} catch (e: Exception) {
LOG.warn("Failed to get Windows machine ID", e)
getFallbackMachineId()
}
}

/**
* Get machine ID on macOS by reading the IOPlatformUUID from IOKit.
*/
private fun getMacOSMachineId(): String {
return try {
val process = ProcessBuilder(
"ioreg", "-rd1", "-c", "IOPlatformExpertDevice"
).start()

val output = process.inputStream.bufferedReader().readText()
val exitCode = process.waitFor()

if (exitCode != 0) {
LOG.warn("ioreg command failed with exit code: $exitCode")
return getFallbackMachineId()
}

// Parse the output to extract IOPlatformUUID
// Output format: "IOPlatformUUID" = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
val regex = Regex("\"IOPlatformUUID\"\\s*=\\s*\"([^\"]+)\"")
val match = regex.find(output)

if (match != null) {
match.groupValues[1]
} else {
LOG.warn("Could not parse IOPlatformUUID from ioreg output")
getFallbackMachineId()
}
} catch (e: Exception) {
LOG.warn("Failed to get macOS machine ID", e)
getFallbackMachineId()
}
}

/**
* Get machine ID on Linux by reading from standard machine-id files.
*/
private fun getLinuxMachineId(): String {
return try {
// Try /var/lib/dbus/machine-id first (D-Bus machine ID)
val dbusFile = File("/var/lib/dbus/machine-id")
if (dbusFile.exists() && dbusFile.canRead()) {
val id = dbusFile.readText().trim()
if (id.isNotEmpty()) {
return id
}
}

// Fall back to /etc/machine-id (systemd machine ID)
val etcFile = File("/etc/machine-id")
if (etcFile.exists() && etcFile.canRead()) {
val id = etcFile.readText().trim()
if (id.isNotEmpty()) {
return id
}
}

LOG.warn("No machine-id file found on Linux")
getFallbackMachineId()
} catch (e: Exception) {
LOG.warn("Failed to get Linux machine ID", e)
getFallbackMachineId()
}
}

/**
* Generate a fallback machine ID based on available system properties.
* This is used when platform-specific methods fail.
*/
private fun getFallbackMachineId(): String {
// Generate a fallback based on available system properties
val props = listOf(
System.getProperty("user.name", "unknown"),
System.getProperty("os.name", "unknown"),
System.getProperty("os.arch", "unknown"),
System.getProperty("user.home", "unknown"),
System.getProperty("java.home", "unknown")
).joinToString("-")

LOG.info("Using fallback machine ID generation")
return props
}

/**
* Hash the raw machine ID using SHA-256.
* This matches the format produced by node-machine-id.
*/
private fun hashMachineId(rawId: String): String {
val digest = MessageDigest.getInstance("SHA-256")
val hashBytes = digest.digest(rawId.toByteArray(Charsets.UTF_8))
return hashBytes.joinToString("") { "%02x".format(it) }
}

/**
* Clear the cached machine ID (useful for testing).
*/
internal fun clearCache() {
cachedMachineId = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package ai.kilocode.jetbrains.util

import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class MachineIdUtilTest {

@Before
fun setUp() {
// Clear the cache before each test to ensure fresh generation
MachineIdUtil.clearCache()
}

@Test
fun `test getMachineId returns non-null value`() {
val machineId = MachineIdUtil.getMachineId()
assertNotNull("Machine ID should not be null", machineId)
}

@Test
fun `test getMachineId returns non-empty value`() {
val machineId = MachineIdUtil.getMachineId()
assertTrue("Machine ID should not be empty", machineId.isNotEmpty())
}

@Test
fun `test getMachineId returns SHA-256 hash format`() {
val machineId = MachineIdUtil.getMachineId()
// SHA-256 produces a 64-character hex string
assertEquals("Machine ID should be 64 characters (SHA-256 hex)", 64, machineId.length)
// Should only contain hex characters
assertTrue(
"Machine ID should only contain hex characters",
machineId.all { it in '0'..'9' || it in 'a'..'f' }
)
}

@Test
fun `test getMachineId returns consistent value`() {
val machineId1 = MachineIdUtil.getMachineId()
val machineId2 = MachineIdUtil.getMachineId()
assertEquals("Machine ID should be consistent across calls", machineId1, machineId2)
}

@Test
fun `test getMachineId caching works`() {
// First call generates the ID
val machineId1 = MachineIdUtil.getMachineId()

// Second call should return cached value
val machineId2 = MachineIdUtil.getMachineId()

assertEquals("Cached machine ID should match original", machineId1, machineId2)
}

@Test
fun `test clearCache allows regeneration`() {
// Get initial machine ID
val machineId1 = MachineIdUtil.getMachineId()

// Clear cache
MachineIdUtil.clearCache()

// Get machine ID again - should regenerate (but will be same value since same machine)
val machineId2 = MachineIdUtil.getMachineId()

// On the same machine, the regenerated ID should be the same
assertEquals("Regenerated machine ID should match on same machine", machineId1, machineId2)
}

@Test
fun `test getMachineId does not contain intellij-machine placeholder`() {
val machineId = MachineIdUtil.getMachineId()
assertNotEquals(
"Machine ID should not be the old placeholder value",
"intellij-machine",
machineId
)
}

@Test
fun `test getMachineId is lowercase hex`() {
val machineId = MachineIdUtil.getMachineId()
assertEquals(
"Machine ID should be lowercase",
machineId.lowercase(),
machineId
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe("ExtensionChannel", () => {
wrapperTitle: null,
wrapperCode: null,
wrapperVersion: null,
machineId: null,
// kilocode_change end
hostname: "test-host",
}
Expand Down
1 change: 1 addition & 0 deletions packages/cloud/src/bridge/__tests__/TaskChannel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe("TaskChannel", () => {
wrapperTitle: null,
wrapperCode: null,
wrapperVersion: null,
machineId: null,
// kilocode_change end
hostname: "test-host",
}
Expand Down
Loading