Skip to content

Commit 5b9c412

Browse files
authored
SystemConfigReader for Apple platforms using NSUserDefaults (#1231)
# Add multiplatform system configuration and secrets readers with JVM-specific implementation Introduce `SystemConfigReader` and `SystemSecretsReader` interfaces to standardize access to configuration values and secrets across different platforms. This change utilizes the `expect`/`actual` mechanism to define common contracts with specific platform implementations. * Implement fully functional readers for the JVM target: the configuration reader resolves values from environment variables and system properties (supports name normalization), while the secrets reader strictly uses environment variables for security. * Add stub implementations for Android, Apple, JS, and WasmJS platforms that currently throw a `NotImplementedError` when accessed. * Define the common interfaces and factory functions to allow retrieval of configuration strings and secrets. * Add `UserDefaultsSystemConfigReader` backed by Apple's NSUserDefaults for retrieving configuration properties. Update `systemConfigReader` to use this implementation and introduce comprehensive unit tests to ensure correctness. ## Motivation and Context Should be used in Debugger Config and LLM Clients See also #1228 ## Breaking Changes No --- #### Type of the changes - [x] New feature (non-breaking change which adds functionality) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Tests improvement - [ ] Refactoring #### Checklist - [x] The pull request has a description of the proposed change - [x] I read the [Contributing Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md) before opening the pull request - [x] The pull request uses **`develop`** as the base branch - [x] Tests for the changes have been added - [x] All new and existing tests passed ##### Additional steps for pull requests adding a new feature - [ ] An issue describing the proposed change exists - [ ] The pull request includes a link to the issue - [ ] The change was discussed and approved in the issue - [ ] Docs have been added / updated
1 parent 86c7e11 commit 5b9c412

File tree

18 files changed

+316
-0
lines changed

18 files changed

+316
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package ai.koog.utils.system
2+
3+
public actual fun systemConfigReader(): SystemConfigReader {
4+
throw NotImplementedError("SystemConfigReader is not yet supported on Android platform")
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package ai.koog.utils.system
2+
3+
public actual fun systemSecretsReader(): SystemSecretsReader {
4+
throw NotImplementedError("SystemSecretsReader is not yet supported on Android platform")
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package ai.koog.utils.system
2+
3+
public actual fun systemConfigReader(): SystemConfigReader = UserDefaultsSystemConfigReader.shared
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package ai.koog.utils.system
2+
3+
public actual fun systemSecretsReader(): SystemSecretsReader {
4+
throw NotImplementedError("SystemSecretsReader is not yet supported on Apple platforms")
5+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package ai.koog.utils.system
2+
3+
import platform.Foundation.NSUserDefaults.Companion.standardUserDefaults
4+
5+
/**
6+
* A configuration reader implementation backed by Apple's `NSUserDefaults`.
7+
*
8+
* This class provides a mechanism to retrieve system configuration properties
9+
* stored in the `NSUserDefaults` system on Apple platforms.
10+
*
11+
* @constructor Initializes an instance of `UserDefaultsSystemConfigReader` with the provided `NSUserDefaults`.
12+
* @param userDefaults The `NSUserDefaults` instance used to fetch configuration values.
13+
*/
14+
public class UserDefaultsSystemConfigReader(
15+
private val userDefaults: platform.Foundation.NSUserDefaults
16+
) : SystemConfigReader {
17+
18+
public companion object {
19+
/**
20+
* A shared instance of [UserDefaultsSystemConfigReader].
21+
*
22+
* This instance provides centralized access to system configuration properties
23+
* stored in Apple's `NSUserDefaults`. It uses the standard `NSUserDefaults` instance
24+
* for fetching configuration values.
25+
*
26+
* The `shared` instance is intended for reuse across the application whenever access
27+
* to system configuration properties is required.
28+
*/
29+
public val shared: UserDefaultsSystemConfigReader = UserDefaultsSystemConfigReader(standardUserDefaults())
30+
}
31+
32+
/**
33+
* Retrieves the value of a system configuration property by its name.
34+
*
35+
* This method interacts with Apple's `NSUserDefaults` system to fetch
36+
* a configuration value based on the provided key.
37+
*
38+
* @param name The name of the configuration property to retrieve.
39+
* @return The value of the configuration property if it exists, or null if not found.
40+
*/
41+
override fun getConfigVariable(name: String): String? {
42+
return userDefaults.stringForKey(name)
43+
}
44+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package ai.koog.utils.system
2+
3+
import io.kotest.matchers.shouldBe
4+
import platform.Foundation.NSUserDefaults.Companion.standardUserDefaults
5+
import kotlin.test.Test
6+
7+
class UserDefaultsSystemConfigReaderTest {
8+
9+
companion object {
10+
init {
11+
val defaults = standardUserDefaults()
12+
defaults.setObject("ho-ho", forKey = "FOO_PROP")
13+
}
14+
}
15+
16+
private val sublect = UserDefaultsSystemConfigReader.shared
17+
18+
@Test
19+
fun testReadValue() {
20+
sublect.getConfigVariable("FOO_PROP") shouldBe "ho-ho"
21+
}
22+
23+
@Test
24+
fun testReadMissingValue() {
25+
sublect.getConfigVariable("BAR_PROP") shouldBe null
26+
}
27+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package ai.koog.utils.system
2+
3+
/**
4+
* An interface for reading system configuration properties.
5+
*
6+
* This interface provides a method to retrieve system configuration values by their names.
7+
* Implementations can vary based on the operating system or platform being used.
8+
*/
9+
public interface SystemConfigReader {
10+
11+
/**
12+
* Retrieves the value of a system configuration property by its name.
13+
*
14+
* @param name The name of the system configuration property to retrieve.
15+
* @return The value of the configuration property if it exists, or null if not found.
16+
*/
17+
public fun getConfigVariable(name: String): String?
18+
}
19+
20+
/**
21+
* Provides an implementation of the [SystemConfigReader] interface for the current platform.
22+
*
23+
* This function returns an instance of [SystemConfigReader] that allows access
24+
* to system-level configuration properties based on platform-specific implementations.
25+
* On unsupported platforms, this function will throw a [NotImplementedError].
26+
*
27+
* @return An instance of [SystemConfigReader] for accessing system configuration properties.
28+
* @throws NotImplementedError if [SystemConfigReader] is not supported on this platform.
29+
*/
30+
public expect fun systemConfigReader(): SystemConfigReader
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package ai.koog.utils.system
2+
3+
/**
4+
* An interface for reading secrets.
5+
*
6+
* This interface provides a method to retrieve secret values by their names.
7+
* Implementations can vary based on the operating system or platform being used.
8+
*/
9+
public interface SystemSecretsReader {
10+
11+
/**
12+
* Retrieves a secret value from environment variables.
13+
*
14+
* **SECURITY NOTICE**:
15+
* Secrets are returned as Strings because the underlying OS APIs
16+
* (environment variables, system properties) return Strings.
17+
* The String cannot be cleared from memory.
18+
*
19+
* **Best practices**:
20+
* - Use platform-specific secure storage when possible (Keychain, KeyStore)
21+
* - Use short-lived tokens instead of long-lived secrets
22+
* - Rotate secrets regularly
23+
* - Don't log or print secret values
24+
*
25+
* For maximum security, consider using:
26+
* - Secrets management services (HashiCorp Vault, AWS Secrets Manager)
27+
* - Platform secure storage APIs
28+
* - Memory-mapped files with OS-level protection
29+
*
30+
* @param name The secret key
31+
* @return The secret value, or null if not found
32+
*/
33+
public fun getSecret(name: String): String?
34+
}
35+
36+
/**
37+
* Provides an implementation of the [SystemSecretsReader] interface for the current platform.
38+
*
39+
* This function returns an instance of [SystemSecretsReader] that allows access
40+
* to secrets based on platform-specific implementations.
41+
* On unsupported platforms, this function will throw a [NotImplementedError].
42+
*
43+
* @return An instance of [SystemSecretsReader] for accessing system configuration properties.
44+
* @throws NotImplementedError if [SystemSecretsReader] is not supported on this platform.
45+
*/
46+
public expect fun systemSecretsReader(): SystemSecretsReader
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package ai.koog.utils.system
2+
3+
public actual fun systemConfigReader(): SystemConfigReader {
4+
throw NotImplementedError("SystemConfigReader is not yet supported on JS platform")
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package ai.koog.utils.system
2+
3+
public actual fun systemSecretsReader(): SystemSecretsReader {
4+
throw NotImplementedError("SystemSecretsReader is not yet supported on JS platform")
5+
}

0 commit comments

Comments
 (0)