Skip to content

Commit 1388d4c

Browse files
wikaaaaacopybara-github
authored andcommitted
feat: add BaseEnvironment interface
PiperOrigin-RevId: 951999702
1 parent 20af4cd commit 1388d4c

2 files changed

Lines changed: 152 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.kt.annotations
18+
19+
/**
20+
* Marks ADK's **environment** APIs (execution environments and the environment toolset) as
21+
* experimental: their shape and semantics may change in future releases without prior notice.
22+
*
23+
* Opt in explicitly with `@OptIn(ExperimentalEnvironmentApi::class)` to use them.
24+
*/
25+
@MustBeDocumented
26+
@Retention(AnnotationRetention.BINARY)
27+
@RequiresOptIn(
28+
level = RequiresOptIn.Level.ERROR,
29+
message =
30+
"ADK environment APIs are experimental and may change at any time. " +
31+
"Opt in with @OptIn(ExperimentalEnvironmentApi::class) to acknowledge the risk.",
32+
)
33+
annotation class ExperimentalEnvironmentApi
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.kt.environment
18+
19+
import com.google.adk.kt.annotations.ExperimentalEnvironmentApi
20+
21+
/**
22+
* Exception thrown by [BaseEnvironment] operations (e.g. a missing file, an unreadable path, or a
23+
* failure to launch a command). The [message] may be surfaced to the model as a tool error, so it
24+
* must be precise and free of sensitive internal detail.
25+
*/
26+
class EnvironmentException(message: String, cause: Throwable? = null) : Exception(message, cause)
27+
28+
/**
29+
* Result of a command execution.
30+
*
31+
* @property exitCode The exit code of the process.
32+
* @property stdout Standard output captured from the process.
33+
* @property stderr Standard error captured from the process.
34+
* @property timedOut Whether the execution exceeded the timeout.
35+
*/
36+
data class ExecutionResult(
37+
val exitCode: Int = 0,
38+
val stdout: String = "",
39+
val stderr: String = "",
40+
val timedOut: Boolean = false,
41+
) {
42+
/** Whether the command completed on its own with a zero exit code. */
43+
val isSuccess: Boolean
44+
get() = exitCode == 0 && !timedOut
45+
}
46+
47+
/**
48+
* Interface for code execution environments.
49+
*
50+
* An environment provides the ability to execute shell commands, read files, and write files within
51+
* a working directory. Concrete implementations include local subprocess execution, sandboxed
52+
* execution, container environments, and cloud-hosted environments.
53+
*
54+
* Methods are `suspend` and may be invoked concurrently from parallel coroutines. This interface
55+
* does no synchronization — when implementing an environment, keep concurrency in mind: overlapping
56+
* operations can race on the shared working directory. Operations report failure by throwing
57+
* [EnvironmentException].
58+
*
59+
* Lifecycle:
60+
* 1. Construct the environment.
61+
* 2. Call [initialize] before first use.
62+
* 3. Use [execute], [readFile], [writeFile].
63+
* 4. Call [close] when done.
64+
*/
65+
@ExperimentalEnvironmentApi
66+
interface BaseEnvironment {
67+
/** The absolute path to the environment's working directory. */
68+
val workingDir: String
69+
70+
/** Whether the environment has been initialized. */
71+
val isInitialized: Boolean
72+
73+
/**
74+
* Initialize the environment (e.g. create the working directory).
75+
*
76+
* Called before first use. The default implementation is a no-op; implementations should ensure
77+
* this method is idempotent.
78+
*/
79+
suspend fun initialize() {}
80+
81+
/**
82+
* Release resources held by the environment.
83+
*
84+
* Called when the environment is no longer needed. The default implementation is a no-op;
85+
* implementations should ensure this method is idempotent.
86+
*/
87+
suspend fun close() {}
88+
89+
/**
90+
* Execute a shell command in the working directory.
91+
*
92+
* Ordinary process failures (non-zero exit, timeout) are returned as an [ExecutionResult]; only a
93+
* failure to launch the command throws [EnvironmentException].
94+
*
95+
* @param command The shell command string to execute.
96+
* @param timeout Maximum execution time in milliseconds; `null` means no limit.
97+
* @return An [ExecutionResult] with exit code, stdout, stderr, and timeout status.
98+
*/
99+
suspend fun execute(command: String, timeout: Long? = null): ExecutionResult
100+
101+
/**
102+
* Read a file from the environment filesystem.
103+
*
104+
* @param path Absolute or working-dir-relative path to the file.
105+
* @return The raw file contents as bytes.
106+
* @throws EnvironmentException if the file does not exist or cannot be read.
107+
*/
108+
suspend fun readFile(path: String): ByteArray
109+
110+
/**
111+
* Write content to a file in the environment's filesystem.
112+
*
113+
* Parent directories are created automatically if they do not exist.
114+
*
115+
* @param path Absolute or working-dir-relative path to the file.
116+
* @param content The raw bytes to write.
117+
*/
118+
suspend fun writeFile(path: String, content: ByteArray)
119+
}

0 commit comments

Comments
 (0)