Skip to content

Commit 1c4b994

Browse files
authored
feat(challenge-sdk-wasm): add terminal host function bindings and types (#26)
Extend the guest-side WASM SDK to support terminal challenge evaluation scenarios requiring process execution, filesystem access, timing, and deterministic random seeds. Terminal types (new term_types.rs module): CommandRequest/CommandResult for process execution with args, env vars, working dir, and timeout; FileReadRequest/FileWriteRequest/FileListRequest with corresponding response types; TaskDefinition for task generation with scoring params and environment config; TermEvaluationMetrics for detailed scoring (execution time, correctness, partial credit, cost); TermEvaluationInput and TermEvaluationOutput wrappers. All types are no_std-compatible using alloc:: only and derive Serialize/Deserialize. Existing types extended: EvaluationInput gains optional task_definition and environment_config fields; EvaluationOutput gains optional metrics field and a with_metrics builder method. Host function bindings (platform_terminal import module): terminal_exec, terminal_read_file, terminal_write_file, terminal_list_dir, terminal_get_time, terminal_random_seed extern declarations with safe Rust wrappers following the existing host_http_get/host_http_post pattern. Also fixes ABI mismatch in http_get which declared 3 params but host expects 4 — adds the missing resp_len parameter. Challenge trait extended with optional lifecycle methods generate_task and setup_environment (with default no-op implementations). The register_challenge! macro now exports these as additional WASM functions using the same ptr/len ABI convention. Allocator arena increased from 1 MiB to 4 MiB to accommodate terminal output capture (stdout/stderr from command execution).
1 parent 814bd4f commit 1c4b994

File tree

5 files changed

+267
-3
lines changed

5 files changed

+267
-3
lines changed

crates/challenge-sdk-wasm/src/alloc_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::cell::UnsafeCell;
22

3-
const ARENA_SIZE: usize = 1024 * 1024; // 1 MiB
3+
const ARENA_SIZE: usize = 4 * 1024 * 1024; // 4 MiB
44

55
struct BumpAllocator {
66
arena: UnsafeCell<[u8; ARENA_SIZE]>,

crates/challenge-sdk-wasm/src/host_functions.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use alloc::vec::Vec;
33

44
#[link(wasm_import_module = "platform_network")]
55
extern "C" {
6-
fn http_get(req_ptr: i32, req_len: i32, resp_ptr: i32) -> i32;
6+
fn http_get(req_ptr: i32, req_len: i32, resp_ptr: i32, resp_len: i32) -> i32;
77
fn http_post(req_ptr: i32, req_len: i32, resp_ptr: i32, resp_len: i32, extra: i32) -> i32;
88
fn dns_resolve(req_ptr: i32, req_len: i32, resp_ptr: i32) -> i32;
99
}
@@ -14,13 +14,24 @@ extern "C" {
1414
fn storage_set(key_ptr: i32, key_len: i32, value_ptr: i32, value_len: i32) -> i32;
1515
}
1616

17+
#[link(wasm_import_module = "platform_terminal")]
18+
extern "C" {
19+
fn terminal_exec(cmd_ptr: i32, cmd_len: i32, result_ptr: i32, result_len: i32) -> i32;
20+
fn terminal_read_file(path_ptr: i32, path_len: i32, buf_ptr: i32, buf_len: i32) -> i32;
21+
fn terminal_write_file(path_ptr: i32, path_len: i32, data_ptr: i32, data_len: i32) -> i32;
22+
fn terminal_list_dir(path_ptr: i32, path_len: i32, buf_ptr: i32, buf_len: i32) -> i32;
23+
fn terminal_get_time() -> i64;
24+
fn terminal_random_seed(buf_ptr: i32, buf_len: i32) -> i32;
25+
}
26+
1727
pub fn host_http_get(request: &[u8]) -> Result<Vec<u8>, i32> {
1828
let mut response_buf = vec![0u8; 65536];
1929
let status = unsafe {
2030
http_get(
2131
request.as_ptr() as i32,
2232
request.len() as i32,
2333
response_buf.as_mut_ptr() as i32,
34+
response_buf.len() as i32,
2435
)
2536
};
2637
if status < 0 {
@@ -94,3 +105,81 @@ pub fn host_storage_set(key: &[u8], value: &[u8]) -> Result<(), i32> {
94105
}
95106
Ok(())
96107
}
108+
109+
pub fn host_terminal_exec(request: &[u8]) -> Result<Vec<u8>, i32> {
110+
let mut result_buf = vec![0u8; 262144];
111+
let status = unsafe {
112+
terminal_exec(
113+
request.as_ptr() as i32,
114+
request.len() as i32,
115+
result_buf.as_mut_ptr() as i32,
116+
result_buf.len() as i32,
117+
)
118+
};
119+
if status < 0 {
120+
return Err(status);
121+
}
122+
result_buf.truncate(status as usize);
123+
Ok(result_buf)
124+
}
125+
126+
pub fn host_read_file(path: &[u8]) -> Result<Vec<u8>, i32> {
127+
let mut buf = vec![0u8; 262144];
128+
let status = unsafe {
129+
terminal_read_file(
130+
path.as_ptr() as i32,
131+
path.len() as i32,
132+
buf.as_mut_ptr() as i32,
133+
buf.len() as i32,
134+
)
135+
};
136+
if status < 0 {
137+
return Err(status);
138+
}
139+
buf.truncate(status as usize);
140+
Ok(buf)
141+
}
142+
143+
pub fn host_write_file(path: &[u8], data: &[u8]) -> Result<(), i32> {
144+
let status = unsafe {
145+
terminal_write_file(
146+
path.as_ptr() as i32,
147+
path.len() as i32,
148+
data.as_ptr() as i32,
149+
data.len() as i32,
150+
)
151+
};
152+
if status < 0 {
153+
return Err(status);
154+
}
155+
Ok(())
156+
}
157+
158+
pub fn host_list_dir(path: &[u8]) -> Result<Vec<u8>, i32> {
159+
let mut buf = vec![0u8; 65536];
160+
let status = unsafe {
161+
terminal_list_dir(
162+
path.as_ptr() as i32,
163+
path.len() as i32,
164+
buf.as_mut_ptr() as i32,
165+
buf.len() as i32,
166+
)
167+
};
168+
if status < 0 {
169+
return Err(status);
170+
}
171+
buf.truncate(status as usize);
172+
Ok(buf)
173+
}
174+
175+
pub fn host_get_time() -> i64 {
176+
unsafe { terminal_get_time() }
177+
}
178+
179+
pub fn host_random_seed(buf: &mut [u8]) -> Result<(), i32> {
180+
let status = unsafe { terminal_random_seed(buf.as_mut_ptr() as i32, buf.len() as i32) };
181+
if status < 0 {
182+
return Err(status);
183+
}
184+
Ok(())
185+
}

crates/challenge-sdk-wasm/src/lib.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@ extern crate alloc;
44

55
pub mod alloc_impl;
66
pub mod host_functions;
7+
pub mod term_types;
78
pub mod types;
89

10+
pub use term_types::*;
911
pub use types::{EvaluationInput, EvaluationOutput};
1012

1113
pub trait Challenge {
1214
fn name(&self) -> &'static str;
1315
fn version(&self) -> &'static str;
1416
fn evaluate(&self, input: EvaluationInput) -> EvaluationOutput;
1517
fn validate(&self, input: EvaluationInput) -> bool;
18+
19+
fn generate_task(&self, _params: &[u8]) -> alloc::vec::Vec<u8> {
20+
alloc::vec::Vec::new()
21+
}
22+
23+
fn setup_environment(&self, _config: &[u8]) -> bool {
24+
true
25+
}
1626
}
1727

1828
/// Pack a pointer and length into a single i64 value.
@@ -25,7 +35,8 @@ pub fn pack_ptr_len(ptr: i32, len: i32) -> i64 {
2535
}
2636

2737
/// Register a [`Challenge`] implementation and export the required WASM ABI
28-
/// functions (`evaluate`, `validate`, `get_name`, `get_version`, and `alloc`).
38+
/// functions (`evaluate`, `validate`, `get_name`, `get_version`,
39+
/// `generate_task`, `setup_environment`, and `alloc`).
2940
///
3041
/// # Usage
3142
///
@@ -119,5 +130,36 @@ macro_rules! register_challenge {
119130
}
120131
ptr as i32
121132
}
133+
134+
#[no_mangle]
135+
pub extern "C" fn generate_task(params_ptr: i32, params_len: i32) -> i64 {
136+
let slice = unsafe {
137+
core::slice::from_raw_parts(params_ptr as *const u8, params_len as usize)
138+
};
139+
let output = <$ty as $crate::Challenge>::generate_task(&_CHALLENGE, slice);
140+
if output.is_empty() {
141+
return $crate::pack_ptr_len(0, 0);
142+
}
143+
let ptr = $crate::alloc_impl::sdk_alloc(output.len());
144+
if ptr.is_null() {
145+
return $crate::pack_ptr_len(0, 0);
146+
}
147+
unsafe {
148+
core::ptr::copy_nonoverlapping(output.as_ptr(), ptr, output.len());
149+
}
150+
$crate::pack_ptr_len(ptr as i32, output.len() as i32)
151+
}
152+
153+
#[no_mangle]
154+
pub extern "C" fn setup_environment(config_ptr: i32, config_len: i32) -> i32 {
155+
let slice = unsafe {
156+
core::slice::from_raw_parts(config_ptr as *const u8, config_len as usize)
157+
};
158+
if <$ty as $crate::Challenge>::setup_environment(&_CHALLENGE, slice) {
159+
1
160+
} else {
161+
0
162+
}
163+
}
122164
};
123165
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use alloc::string::String;
2+
use alloc::vec::Vec;
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Clone, Debug, Serialize, Deserialize)]
6+
pub struct CommandRequest {
7+
pub command: String,
8+
pub args: Vec<String>,
9+
pub env_vars: Vec<(String, String)>,
10+
pub working_dir: Option<String>,
11+
pub timeout_ms: u64,
12+
}
13+
14+
#[derive(Clone, Debug, Serialize, Deserialize)]
15+
pub struct CommandResult {
16+
pub exit_code: i32,
17+
pub stdout: Vec<u8>,
18+
pub stderr: Vec<u8>,
19+
pub execution_time_ms: u64,
20+
}
21+
22+
#[derive(Clone, Debug, Serialize, Deserialize)]
23+
pub struct FileReadRequest {
24+
pub path: String,
25+
}
26+
27+
#[derive(Clone, Debug, Serialize, Deserialize)]
28+
pub struct FileReadResponse {
29+
pub data: Vec<u8>,
30+
pub success: bool,
31+
pub error: Option<String>,
32+
}
33+
34+
#[derive(Clone, Debug, Serialize, Deserialize)]
35+
pub struct FileWriteRequest {
36+
pub path: String,
37+
pub data: Vec<u8>,
38+
}
39+
40+
#[derive(Clone, Debug, Serialize, Deserialize)]
41+
pub struct FileWriteResponse {
42+
pub success: bool,
43+
pub bytes_written: u64,
44+
pub error: Option<String>,
45+
}
46+
47+
#[derive(Clone, Debug, Serialize, Deserialize)]
48+
pub struct FileListRequest {
49+
pub path: String,
50+
}
51+
52+
#[derive(Clone, Debug, Serialize, Deserialize)]
53+
pub struct FileListResponse {
54+
pub entries: Vec<FileEntry>,
55+
pub success: bool,
56+
pub error: Option<String>,
57+
}
58+
59+
#[derive(Clone, Debug, Serialize, Deserialize)]
60+
pub struct FileEntry {
61+
pub name: String,
62+
pub is_dir: bool,
63+
pub size: u64,
64+
}
65+
66+
#[derive(Clone, Debug, Serialize, Deserialize)]
67+
pub struct TaskDefinition {
68+
pub task_id: String,
69+
pub description: String,
70+
pub expected_output_hash: Option<Vec<u8>>,
71+
pub environment_config: Vec<u8>,
72+
pub scoring_params: Vec<u8>,
73+
}
74+
75+
#[derive(Clone, Debug, Serialize, Deserialize)]
76+
pub struct TermEvaluationMetrics {
77+
pub execution_time_ms: u64,
78+
pub correctness_score: f64,
79+
pub partial_credit: f64,
80+
pub cost: f64,
81+
}
82+
83+
#[derive(Clone, Debug, Serialize, Deserialize)]
84+
pub struct TermEvaluationInput {
85+
pub agent_data: Vec<u8>,
86+
pub challenge_id: String,
87+
pub params: Vec<u8>,
88+
pub task_definition: Option<Vec<u8>>,
89+
pub environment_config: Option<Vec<u8>>,
90+
}
91+
92+
#[derive(Clone, Debug, Serialize, Deserialize)]
93+
pub struct TermEvaluationOutput {
94+
pub score: i64,
95+
pub valid: bool,
96+
pub message: String,
97+
pub metrics: Option<Vec<u8>>,
98+
}
99+
100+
impl TermEvaluationOutput {
101+
pub fn success(score: i64, message: &str) -> Self {
102+
Self {
103+
score,
104+
valid: true,
105+
message: String::from(message),
106+
metrics: None,
107+
}
108+
}
109+
110+
pub fn failure(message: &str) -> Self {
111+
Self {
112+
score: 0,
113+
valid: false,
114+
message: String::from(message),
115+
metrics: None,
116+
}
117+
}
118+
119+
pub fn with_metrics(mut self, metrics: Vec<u8>) -> Self {
120+
self.metrics = Some(metrics);
121+
self
122+
}
123+
}

crates/challenge-sdk-wasm/src/types.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ pub struct EvaluationInput {
77
pub agent_data: Vec<u8>,
88
pub challenge_id: String,
99
pub params: Vec<u8>,
10+
pub task_definition: Option<Vec<u8>>,
11+
pub environment_config: Option<Vec<u8>>,
1012
}
1113

1214
#[derive(Clone, Debug, Serialize, Deserialize)]
1315
pub struct EvaluationOutput {
1416
pub score: i64,
1517
pub valid: bool,
1618
pub message: String,
19+
pub metrics: Option<Vec<u8>>,
1720
}
1821

1922
impl EvaluationOutput {
@@ -22,6 +25,7 @@ impl EvaluationOutput {
2225
score,
2326
valid: true,
2427
message: String::from(message),
28+
metrics: None,
2529
}
2630
}
2731

@@ -30,6 +34,12 @@ impl EvaluationOutput {
3034
score: 0,
3135
valid: false,
3236
message: String::from(message),
37+
metrics: None,
3338
}
3439
}
40+
41+
pub fn with_metrics(mut self, metrics: Vec<u8>) -> Self {
42+
self.metrics = Some(metrics);
43+
self
44+
}
3545
}

0 commit comments

Comments
 (0)