Implemented the core infrastructure for the Code Execution Engine#45
Implemented the core infrastructure for the Code Execution Engine#45orpiske merged 1 commit intowanaku-ai:mainfrom
Conversation
- Implemented the exchange data types for the Code Execution Engine - Adjusted the ServiceTarget for the Code Execution Engine - Implemented data types for the Code Execution Engine - Added a Code Execution Engine service type - Added a more fields to the ServiceTarget This solves issue wanaku-ai#42
Reviewer's GuideIntroduces core data and exchange types plus service metadata changes to support a new sandboxed Code Execution Engine and string-based service type identifiers across discovery and persistence components. Sequence diagram for Code Execution Engine request and SSE streamingsequenceDiagram
actor Client
participant Api as CodeExecutionAPI
participant Engine as ExecutionEngine
participant Store as TaskStore
participant Stream as SSEStream
Client->>Api: POST /code-execution request(CodeExecutionRequest)
Api->>+Engine: submit(CodeExecutionRequest)
Engine->>Engine: create CodeExecutionTask
Engine->>Store: save(CodeExecutionTask)
Engine-->>Api: CodeExecutionResponse(taskId, streamUrl, PENDING)
Api-->>Client: 202 Accepted with CodeExecutionResponse
Client->>Stream: GET streamUrl (SSE)
Stream->>Store: load(taskId)
Store-->>Stream: CodeExecutionTask
Engine->>Engine: start execution
Engine->>Stream: send CodeExecutionEvent.started(taskId)
loop while running
Engine->>Stream: send CodeExecutionEvent.output(taskId, chunk)
alt error output
Engine->>Stream: send CodeExecutionEvent.error(taskId, stderrChunk)
end
end
alt success
Engine->>Stream: send CodeExecutionEvent.completed(taskId, exitCode)
else failure
Engine->>Stream: send CodeExecutionEvent.failed(taskId, exitCode, message)
else timeout
Engine->>Stream: send CodeExecutionEvent.timeout(taskId)
else cancelled
Engine->>Stream: send CodeExecutionEvent.cancelled(taskId)
end
Stream-->>Client: SSE event stream closes
Class diagram for updated ServiceTarget and ServiceTypeclassDiagram
class ServiceTarget {
+String id
+String serviceName
+String host
+int port
+String serviceType
+String serviceSubType
+String languageName
+String languageType
+String languageSubType
+ServiceTarget()
+ServiceTarget(String id, String serviceName, String host, int port, String serviceType, String serviceSubType, String languageName, String languageType, String languageSubType)
+String getServiceName()
+void setServiceName(String serviceName)
+String getHost()
+int getPort()
+String getServiceType()
+void setServiceType(String serviceType)
+String getServiceSubType()
+void setServiceSubType(String serviceSubType)
+String getLanguageName()
+void setLanguageName(String languageName)
+String getLanguageType()
+void setLanguageType(String languageType)
+String getLanguageSubType()
+void setLanguageSubType(String languageSubType)
+String getAddress()
+boolean equals(Object o)
+int hashCode()
+String toString()
+static ServiceTarget newEmptyTarget(String serviceName, String address, int port, String serviceType, String serviceSubType, String languageName, String languageType, String languageSubType)
+static ServiceTarget newEmptyTarget(String serviceName, String address, int port, String serviceType)
}
class ServiceType {
<<enum>>
RESOURCE_PROVIDER
TOOL_INVOKER
MULTI_CAPABILITY
CODE_EXECUTION_ENGINE
+String asValue()
+int asIntValue()
+static ServiceType fromValue(String value)
+static ServiceType fromIntValue(int value)
}
class InstanceDataManager {
+InstanceDataManager(String dataDir, String serviceName)
+void writeEntry(ServiceTarget serviceTarget)
-static FileHeader newFileHeader(ServiceTarget serviceTarget)
}
class FileHeader {
<<enum>>
TOOL_INVOKER
RESOURCE_PROVIDER
MULTI_CAPABILITY
}
ServiceTarget ..> ServiceType : legacy enum reference
InstanceDataManager ..> ServiceTarget : uses
InstanceDataManager ..> FileHeader : maps serviceType string
Class diagram for core Code Execution Engine data typesclassDiagram
class CodeExecutionRequest {
+static long DEFAULT_TIMEOUT_MS
+static int MAX_CODE_SIZE_BYTES
+static long MAX_TIMEOUT_MS
+static long MIN_TIMEOUT_MS
-String code
-Long timeout
-Map~String, String~ environment
-List~String~ arguments
-Map~String, Object~ metadata
+CodeExecutionRequest()
+CodeExecutionRequest(String code)
+String getCode()
+void setCode(String code)
+Long getTimeout()
+void setTimeout(Long timeout)
+Map~String, String~ getEnvironment()
+void setEnvironment(Map~String, String~ environment)
+List~String~ getArguments()
+void setArguments(List~String~ arguments)
+Map~String, Object~ getMetadata()
+void setMetadata(Map~String, Object~ metadata)
+void validate()
+boolean equals(Object o)
+int hashCode()
+String toString()
}
class CodeExecutionStatus {
<<enum>>
PENDING
RUNNING
COMPLETED
FAILED
CANCELLED
TIMEOUT
+boolean isTerminal()
+boolean isSuccess()
+boolean isError()
}
class CodeExecutionEventType {
<<enum>>
STARTED
OUTPUT
ERROR
COMPLETED
FAILED
CANCELLED
TIMEOUT
+boolean isTerminal()
+boolean isOutput()
}
class CodeExecutionEvent {
-CodeExecutionEventType eventType
-String taskId
-Instant timestamp
-CodeExecutionStatus status
-String output
-String error
-Integer exitCode
-String message
-Map~String, Object~ metadata
+CodeExecutionEvent()
+CodeExecutionEvent(CodeExecutionEventType eventType, String taskId, CodeExecutionStatus status)
+CodeExecutionEventType getEventType()
+void setEventType(CodeExecutionEventType eventType)
+String getTaskId()
+void setTaskId(String taskId)
+Instant getTimestamp()
+void setTimestamp(Instant timestamp)
+CodeExecutionStatus getStatus()
+void setStatus(CodeExecutionStatus status)
+String getOutput()
+void setOutput(String output)
+String getError()
+void setError(String error)
+Integer getExitCode()
+void setExitCode(Integer exitCode)
+String getMessage()
+void setMessage(String message)
+Map~String, Object~ getMetadata()
+void setMetadata(Map~String, Object~ metadata)
+static CodeExecutionEvent started(String taskId)
+static CodeExecutionEvent output(String taskId, String output)
+static CodeExecutionEvent error(String taskId, String error)
+static CodeExecutionEvent completed(String taskId, int exitCode)
+static CodeExecutionEvent failed(String taskId, int exitCode, String errorMessage)
+static CodeExecutionEvent timeout(String taskId)
+static CodeExecutionEvent cancelled(String taskId)
+boolean equals(Object o)
+int hashCode()
+String toString()
}
class CodeExecutionTask {
-String taskId
-CodeExecutionRequest request
-String engineType
-String language
-CodeExecutionStatus status
-Instant submittedAt
-Instant startedAt
-Instant completedAt
-Integer exitCode
+CodeExecutionTask()
+CodeExecutionTask(String taskId, CodeExecutionRequest request, String engineType, String language)
+String getTaskId()
+void setTaskId(String taskId)
+CodeExecutionRequest getRequest()
+void setRequest(CodeExecutionRequest request)
+String getEngineType()
+void setEngineType(String engineType)
+String getLanguage()
+void setLanguage(String language)
+CodeExecutionStatus getStatus()
+void setStatus(CodeExecutionStatus status)
+Instant getSubmittedAt()
+void setSubmittedAt(Instant submittedAt)
+Instant getStartedAt()
+void setStartedAt(Instant startedAt)
+Instant getCompletedAt()
+void setCompletedAt(Instant completedAt)
+Integer getExitCode()
+void setExitCode(Integer exitCode)
+void markStarted()
+void markCompleted(int exitCode)
+void markFailed(int exitCode)
+void markTimeout()
+void markCancelled()
+Long getExecutionDurationMs()
+boolean isTerminal()
+boolean equals(Object o)
+int hashCode()
+String toString()
}
class CodeExecutionResponse {
<<record>>
+String taskId
+String streamUrl
+CodeExecutionStatus status
+Instant submittedAt
+static CodeExecutionResponse create(String taskId, String streamUrl, CodeExecutionStatus status)
+static CodeExecutionResponse createPending(String taskId, String streamUrl)
}
class CodeExecutionError {
+String error
+String message
+String taskId
+Long timestamp
+Map~String, String~ details
+CodeExecutionError()
+CodeExecutionError(String error, String message)
+CodeExecutionError(String error, String message, String taskId)
+String getError()
+void setError(String error)
+String getMessage()
+void setMessage(String message)
+String getTaskId()
+void setTaskId(String taskId)
+Long getTimestamp()
+void setTimestamp(Long timestamp)
+Map~String, String~ getDetails()
+void setDetails(Map~String, String~ details)
+String toString()
}
CodeExecutionTask --> CodeExecutionRequest : contains
CodeExecutionTask --> CodeExecutionStatus : uses
CodeExecutionEvent --> CodeExecutionEventType : uses
CodeExecutionEvent --> CodeExecutionStatus : uses
CodeExecutionResponse --> CodeExecutionStatus : uses
State diagram for CodeExecutionStatus lifecyclestateDiagram-v2
[*] --> PENDING
PENDING --> RUNNING: start execution
PENDING --> CANCELLED: cancel before start
RUNNING --> COMPLETED: execution finished
RUNNING --> FAILED: execution error
RUNNING --> TIMEOUT: exceeded timeout
RUNNING --> CANCELLED: user cancelled
COMPLETED --> [*]
FAILED --> [*]
TIMEOUT --> [*]
CANCELLED --> [*]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In CodeExecutionTask.equals,
language == that.languagecompares String references rather than values; this should useObjects.equals(language, that.language)to behave correctly. - The switch in InstanceDataManager.newFileHeader uses hard-coded serviceType strings (e.g. "tool-invoker", "code-execution-engine"); consider centralizing these identifiers (e.g. via ServiceType or constants) to avoid drift between callers and the mapping logic.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In CodeExecutionTask.equals, `language == that.language` compares String references rather than values; this should use `Objects.equals(language, that.language)` to behave correctly.
- The switch in InstanceDataManager.newFileHeader uses hard-coded serviceType strings (e.g. "tool-invoker", "code-execution-engine"); consider centralizing these identifiers (e.g. via ServiceType or constants) to avoid drift between callers and the mapping logic.
## Individual Comments
### Comment 1
<location> `capabilities-api/src/main/java/ai/wanaku/capabilities/sdk/api/types/execution/CodeExecutionTask.java:292-295` </location>
<code_context>
+ return false;
+ }
+ CodeExecutionTask that = (CodeExecutionTask) o;
+ return Objects.equals(taskId, that.taskId)
+ && Objects.equals(request, that.request)
+ && Objects.equals(engineType, that.engineType)
+ && language == that.language
+ && status == that.status
+ && Objects.equals(submittedAt, that.submittedAt)
</code_context>
<issue_to_address>
**issue (bug_risk):** Use `Objects.equals` for comparing `language` instead of `==` to avoid incorrect equality behavior.
In this `equals` implementation, `language` is a `String` but is compared with `==`:
```java
&& language == that.language
```
This checks reference equality, not value equality, so two objects with the same language content may be considered unequal. This breaks the contract of `equals` and can cause subtle bugs when these objects are used in collections or comparisons.
</issue_to_address>
### Comment 2
<location> `capabilities-data-files/src/test/java/ai/wanaku/capabilities/sdk/data/files/InstanceDataManagerTest.java:52` </location>
<code_context>
final String expectedID = UUID.randomUUID().toString();
ServiceTarget serviceTarget = ServiceTarget
- .newEmptyTarget("testService", "localhost", 9190, ServiceType.TOOL_INVOKER);
+ .newEmptyTarget("testService", "localhost", 9190, "tool-invoker");
serviceTarget.setId(expectedID);
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for the new string-based serviceType mapping, including null and unknown values
The production code now maps string service types to `FileHeader` and throws `IllegalArgumentException` for `null` or unknown values (with a special case for `"code-execution-engine"`). The current test only covers the happy path for `"tool-invoker"`. Please extend `InstanceDataManagerTest` with:
- A test that `"resource-provider"`, `"tool-invoker"`, and `"multi-capability"` each produce the expected `FileHeader`.
- A test that `"code-execution-engine"` is treated as `FileHeader.MULTI_CAPABILITY`.
- A test that `serviceType == null` throws `IllegalArgumentException`.
- A test that an unknown string (e.g. `"unknown-service"`) throws `IllegalArgumentException`.
Suggested implementation:
```java
import ai.wanaku.capabilities.sdk.api.types.providers.ServiceTarget;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
```
```java
final String expectedID = UUID.randomUUID().toString();
// Valid service types should be accepted
assertDoesNotThrow(
() -> ServiceTarget.newEmptyTarget("testService", "localhost", 9190, "resource-provider"));
assertDoesNotThrow(
() -> ServiceTarget.newEmptyTarget("testService", "localhost", 9190, "tool-invoker"));
assertDoesNotThrow(
() -> ServiceTarget.newEmptyTarget("testService", "localhost", 9190, "multi-capability"));
// "code-execution-engine" should be treated as MULTI_CAPABILITY and therefore also be accepted
assertDoesNotThrow(
() -> ServiceTarget.newEmptyTarget("testService", "localhost", 9190, "code-execution-engine"));
// Null and unknown service types should be rejected
assertThrows(
IllegalArgumentException.class,
() -> ServiceTarget.newEmptyTarget("testService", "localhost", 9190, null));
assertThrows(
IllegalArgumentException.class,
() -> ServiceTarget.newEmptyTarget("testService", "localhost", 9190, "unknown-service"));
ServiceTarget serviceTarget = ServiceTarget
.newEmptyTarget("testService", "localhost", 9190, "tool-invoker");
```
To fully align with the review comment and explicitly verify the `FileHeader` mapping (not just that the calls succeed/fail), you will also need to:
1. Expose or access the mapped `FileHeader` for a given `ServiceTarget` or service type (e.g., via a getter on `ServiceTarget`, a helper on `InstanceDataManager`, or by inspecting the generated instance data file).
2. Add separate `@Test` methods in `InstanceDataManagerTest` that:
- Assert `"resource-provider"`, `"tool-invoker"`, and `"multi-capability"` each map to their expected `FileHeader` value.
- Assert `"code-execution-engine"` maps to `FileHeader.MULTI_CAPABILITY`.
The exact assertions depend on how `FileHeader` is surfaced in your test (e.g., `assertEquals(FileHeader.RESOURCE_PROVIDER, target.getFileHeader())`). Insert these new test methods at the class level of `InstanceDataManagerTest`, respecting the existing test structure and naming conventions.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| return Objects.equals(taskId, that.taskId) | ||
| && Objects.equals(request, that.request) | ||
| && Objects.equals(engineType, that.engineType) | ||
| && language == that.language |
There was a problem hiding this comment.
issue (bug_risk): Use Objects.equals for comparing language instead of == to avoid incorrect equality behavior.
In this equals implementation, language is a String but is compared with ==:
&& language == that.languageThis checks reference equality, not value equality, so two objects with the same language content may be considered unequal. This breaks the contract of equals and can cause subtle bugs when these objects are used in collections or comparisons.
This solves issue #42
Summary by Sourcery
Introduce core data model for the Code Execution Engine and extend service discovery metadata to support code execution services.
New Features:
Enhancements: