mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 05:02:11 +00:00
docs: add mcp and skill session config (#106)
This commit is contained in:
parent
d236edf35c
commit
4c8d93e077
95 changed files with 10014 additions and 1342 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed architecture documentation covering the daemon, agent schema pipeline, session management, agent execution patterns, and SDK modes.
|
||||
|
||||
## Skill Source Installation
|
||||
|
||||
Skills are installed via `skills.sources` in the session create request. The [vercel-labs/skills](https://github.com/vercel-labs/skills) repo (`~/misc/skills`) provides reference for skill installation patterns and source parsing logic. The server handles fetching GitHub repos (via zip download) and git repos (via clone) to `~/.sandbox-agent/skills-cache/`, discovering `SKILL.md` files, and symlinking into agent skill roots.
|
||||
|
||||
# Server Testing
|
||||
|
||||
## Test placement
|
||||
|
|
|
|||
|
|
@ -743,7 +743,13 @@ fn parse_version_output(output: &std::process::Output) -> Option<String> {
|
|||
.lines()
|
||||
.map(str::trim)
|
||||
.find(|line| !line.is_empty())
|
||||
.map(|line| line.to_string())
|
||||
.map(|line| {
|
||||
// Strip trailing metadata like " (released ...)" from version strings
|
||||
match line.find(" (") {
|
||||
Some(pos) => line[..pos].to_string(),
|
||||
None => line.to_string(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_jsonl(text: &str) -> Vec<Value> {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ tracing-logfmt.workspace = true
|
|||
tracing-subscriber.workspace = true
|
||||
include_dir.workspace = true
|
||||
base64.workspace = true
|
||||
toml_edit.workspace = true
|
||||
tar.workspace = true
|
||||
zip.workspace = true
|
||||
tempfile = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command as ProcessCommand, Stdio};
|
||||
|
|
@ -13,12 +14,14 @@ mod build_version {
|
|||
}
|
||||
use crate::router::{build_router_with_state, shutdown_servers};
|
||||
use crate::router::{
|
||||
AgentInstallRequest, AppState, AuthConfig, BrandingMode, CreateSessionRequest, MessageRequest,
|
||||
PermissionReply, PermissionReplyRequest, QuestionReplyRequest,
|
||||
AgentInstallRequest, AppState, AuthConfig, BrandingMode, CreateSessionRequest, McpServerConfig,
|
||||
MessageRequest, PermissionReply, PermissionReplyRequest, QuestionReplyRequest, SkillSource,
|
||||
SkillsConfig,
|
||||
};
|
||||
use crate::router::{
|
||||
AgentListResponse, AgentModelsResponse, AgentModesResponse, CreateSessionResponse,
|
||||
EventsResponse, SessionListResponse,
|
||||
AgentListResponse, AgentModelsResponse, AgentModesResponse, CreateSessionResponse, EventsResponse,
|
||||
FsActionResponse, FsEntry, FsMoveRequest, FsMoveResponse, FsStat, FsUploadBatchResponse,
|
||||
FsWriteResponse, SessionListResponse,
|
||||
};
|
||||
use crate::server_logs::ServerLogs;
|
||||
use crate::telemetry;
|
||||
|
|
@ -176,6 +179,10 @@ pub struct DaemonStartArgs {
|
|||
|
||||
#[arg(long, short = 'p', default_value_t = DEFAULT_PORT)]
|
||||
port: u16,
|
||||
|
||||
/// If the daemon is already running but outdated, stop and restart it.
|
||||
#[arg(long, default_value_t = false)]
|
||||
upgrade: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
|
|
@ -202,6 +209,8 @@ pub enum ApiCommand {
|
|||
Agents(AgentsArgs),
|
||||
/// Create sessions and interact with session events.
|
||||
Sessions(SessionsArgs),
|
||||
/// Manage filesystem entries.
|
||||
Fs(FsArgs),
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
|
|
@ -225,6 +234,12 @@ pub struct SessionsArgs {
|
|||
command: SessionsCommand,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct FsArgs {
|
||||
#[command(subcommand)]
|
||||
command: FsCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum AgentsCommand {
|
||||
/// List all agents and install status.
|
||||
|
|
@ -272,6 +287,27 @@ pub enum SessionsCommand {
|
|||
ReplyPermission(PermissionReplyArgs),
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum FsCommand {
|
||||
/// List directory entries.
|
||||
Entries(FsEntriesArgs),
|
||||
/// Read a file.
|
||||
Read(FsReadArgs),
|
||||
/// Write a file.
|
||||
Write(FsWriteArgs),
|
||||
/// Delete a file or directory.
|
||||
Delete(FsDeleteArgs),
|
||||
/// Create a directory.
|
||||
Mkdir(FsMkdirArgs),
|
||||
/// Move a file or directory.
|
||||
Move(FsMoveArgs),
|
||||
/// Stat a file or directory.
|
||||
Stat(FsStatArgs),
|
||||
/// Upload a tar archive and extract it.
|
||||
#[command(name = "upload-batch")]
|
||||
UploadBatch(FsUploadBatchArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct ClientArgs {
|
||||
#[arg(long, short = 'e')]
|
||||
|
|
@ -323,6 +359,10 @@ pub struct CreateSessionArgs {
|
|||
variant: Option<String>,
|
||||
#[arg(long, short = 'A')]
|
||||
agent_version: Option<String>,
|
||||
#[arg(long)]
|
||||
mcp_config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
skill: Vec<PathBuf>,
|
||||
#[command(flatten)]
|
||||
client: ClientArgs,
|
||||
}
|
||||
|
|
@ -406,6 +446,91 @@ pub struct PermissionReplyArgs {
|
|||
client: ClientArgs,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct FsEntriesArgs {
|
||||
#[arg(long)]
|
||||
path: Option<String>,
|
||||
#[arg(long)]
|
||||
session_id: Option<String>,
|
||||
#[command(flatten)]
|
||||
client: ClientArgs,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct FsReadArgs {
|
||||
path: String,
|
||||
#[arg(long)]
|
||||
session_id: Option<String>,
|
||||
#[command(flatten)]
|
||||
client: ClientArgs,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct FsWriteArgs {
|
||||
path: String,
|
||||
#[arg(long)]
|
||||
content: Option<String>,
|
||||
#[arg(long = "from-file")]
|
||||
from_file: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
session_id: Option<String>,
|
||||
#[command(flatten)]
|
||||
client: ClientArgs,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct FsDeleteArgs {
|
||||
path: String,
|
||||
#[arg(long)]
|
||||
recursive: bool,
|
||||
#[arg(long)]
|
||||
session_id: Option<String>,
|
||||
#[command(flatten)]
|
||||
client: ClientArgs,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct FsMkdirArgs {
|
||||
path: String,
|
||||
#[arg(long)]
|
||||
session_id: Option<String>,
|
||||
#[command(flatten)]
|
||||
client: ClientArgs,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct FsMoveArgs {
|
||||
from: String,
|
||||
to: String,
|
||||
#[arg(long)]
|
||||
overwrite: bool,
|
||||
#[arg(long)]
|
||||
session_id: Option<String>,
|
||||
#[command(flatten)]
|
||||
client: ClientArgs,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct FsStatArgs {
|
||||
path: String,
|
||||
#[arg(long)]
|
||||
session_id: Option<String>,
|
||||
#[command(flatten)]
|
||||
client: ClientArgs,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct FsUploadBatchArgs {
|
||||
#[arg(long = "tar")]
|
||||
tar_path: PathBuf,
|
||||
#[arg(long)]
|
||||
path: Option<String>,
|
||||
#[arg(long)]
|
||||
session_id: Option<String>,
|
||||
#[command(flatten)]
|
||||
client: ClientArgs,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct CredentialsExtractArgs {
|
||||
#[arg(long, short = 'a', value_enum)]
|
||||
|
|
@ -433,6 +558,8 @@ pub struct CredentialsExtractEnvArgs {
|
|||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CliError {
|
||||
#[error("missing --token or --no-token for server mode")]
|
||||
MissingToken,
|
||||
#[error("invalid cors origin: {0}")]
|
||||
InvalidCorsOrigin(String),
|
||||
#[error("invalid cors method: {0}")]
|
||||
|
|
@ -590,6 +717,7 @@ fn run_api(command: &ApiCommand, cli: &CliConfig) -> Result<(), CliError> {
|
|||
match command {
|
||||
ApiCommand::Agents(subcommand) => run_agents(&subcommand.command, cli),
|
||||
ApiCommand::Sessions(subcommand) => run_sessions(&subcommand.command, cli),
|
||||
ApiCommand::Fs(subcommand) => run_fs(&subcommand.command, cli),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -672,6 +800,9 @@ fn run_opencode(cli: &CliConfig, args: &OpencodeArgs) -> Result<(), CliError> {
|
|||
fn run_daemon(command: &DaemonCommand, cli: &CliConfig) -> Result<(), CliError> {
|
||||
let token = cli.token.as_deref();
|
||||
match command {
|
||||
DaemonCommand::Start(args) if args.upgrade => {
|
||||
crate::daemon::ensure_running(cli, &args.host, args.port, token)
|
||||
}
|
||||
DaemonCommand::Start(args) => crate::daemon::start(cli, &args.host, args.port, token),
|
||||
DaemonCommand::Stop(args) => crate::daemon::stop(&args.host, args.port),
|
||||
DaemonCommand::Status(args) => {
|
||||
|
|
@ -722,6 +853,33 @@ fn run_sessions(command: &SessionsCommand, cli: &CliConfig) -> Result<(), CliErr
|
|||
}
|
||||
SessionsCommand::Create(args) => {
|
||||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let mcp = if let Some(path) = &args.mcp_config {
|
||||
let text = std::fs::read_to_string(path)?;
|
||||
let parsed =
|
||||
serde_json::from_str::<std::collections::BTreeMap<String, McpServerConfig>>(
|
||||
&text,
|
||||
)?;
|
||||
Some(parsed)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let skills = if args.skill.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(SkillsConfig {
|
||||
sources: args
|
||||
.skill
|
||||
.iter()
|
||||
.map(|path| SkillSource {
|
||||
source_type: "local".to_string(),
|
||||
source: path.to_string_lossy().to_string(),
|
||||
skills: None,
|
||||
git_ref: None,
|
||||
subpath: None,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
};
|
||||
let body = CreateSessionRequest {
|
||||
agent: args.agent.clone(),
|
||||
agent_mode: args.agent_mode.clone(),
|
||||
|
|
@ -731,6 +889,8 @@ fn run_sessions(command: &SessionsCommand, cli: &CliConfig) -> Result<(), CliErr
|
|||
agent_version: args.agent_version.clone(),
|
||||
directory: None,
|
||||
title: None,
|
||||
mcp,
|
||||
skills,
|
||||
};
|
||||
let path = format!("{API_PREFIX}/sessions/{}", args.session_id);
|
||||
let response = ctx.post(&path, &body)?;
|
||||
|
|
@ -740,6 +900,7 @@ fn run_sessions(command: &SessionsCommand, cli: &CliConfig) -> Result<(), CliErr
|
|||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let body = MessageRequest {
|
||||
message: args.message.clone(),
|
||||
attachments: Vec::new(),
|
||||
};
|
||||
let path = format!("{API_PREFIX}/sessions/{}/messages", args.session_id);
|
||||
let response = ctx.post(&path, &body)?;
|
||||
|
|
@ -749,6 +910,7 @@ fn run_sessions(command: &SessionsCommand, cli: &CliConfig) -> Result<(), CliErr
|
|||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let body = MessageRequest {
|
||||
message: args.message.clone(),
|
||||
attachments: Vec::new(),
|
||||
};
|
||||
let path = format!("{API_PREFIX}/sessions/{}/messages/stream", args.session_id);
|
||||
let response = ctx.post_with_query(
|
||||
|
|
@ -845,6 +1007,129 @@ fn run_sessions(command: &SessionsCommand, cli: &CliConfig) -> Result<(), CliErr
|
|||
}
|
||||
}
|
||||
|
||||
fn run_fs(command: &FsCommand, cli: &CliConfig) -> Result<(), CliError> {
|
||||
match command {
|
||||
FsCommand::Entries(args) => {
|
||||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let response = ctx.get_with_query(
|
||||
&format!("{API_PREFIX}/fs/entries"),
|
||||
&[
|
||||
("path", args.path.clone()),
|
||||
("session_id", args.session_id.clone()),
|
||||
],
|
||||
)?;
|
||||
print_json_response::<Vec<FsEntry>>(response)
|
||||
}
|
||||
FsCommand::Read(args) => {
|
||||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let response = ctx.get_with_query(
|
||||
&format!("{API_PREFIX}/fs/file"),
|
||||
&[
|
||||
("path", Some(args.path.clone())),
|
||||
("session_id", args.session_id.clone()),
|
||||
],
|
||||
)?;
|
||||
print_binary_response(response)
|
||||
}
|
||||
FsCommand::Write(args) => {
|
||||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let body = match (&args.content, &args.from_file) {
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(CliError::Server(
|
||||
"use --content or --from-file, not both".to_string(),
|
||||
))
|
||||
}
|
||||
(None, None) => {
|
||||
return Err(CliError::Server(
|
||||
"write requires --content or --from-file".to_string(),
|
||||
))
|
||||
}
|
||||
(Some(content), None) => content.clone().into_bytes(),
|
||||
(None, Some(path)) => std::fs::read(path)?,
|
||||
};
|
||||
let response = ctx.put_raw_with_query(
|
||||
&format!("{API_PREFIX}/fs/file"),
|
||||
body,
|
||||
"application/octet-stream",
|
||||
&[
|
||||
("path", Some(args.path.clone())),
|
||||
("session_id", args.session_id.clone()),
|
||||
],
|
||||
)?;
|
||||
print_json_response::<FsWriteResponse>(response)
|
||||
}
|
||||
FsCommand::Delete(args) => {
|
||||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let response = ctx.delete_with_query(
|
||||
&format!("{API_PREFIX}/fs/entry"),
|
||||
&[
|
||||
("path", Some(args.path.clone())),
|
||||
("session_id", args.session_id.clone()),
|
||||
(
|
||||
"recursive",
|
||||
if args.recursive {
|
||||
Some("true".to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
],
|
||||
)?;
|
||||
print_json_response::<FsActionResponse>(response)
|
||||
}
|
||||
FsCommand::Mkdir(args) => {
|
||||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let response = ctx.post_empty_with_query(
|
||||
&format!("{API_PREFIX}/fs/mkdir"),
|
||||
&[
|
||||
("path", Some(args.path.clone())),
|
||||
("session_id", args.session_id.clone()),
|
||||
],
|
||||
)?;
|
||||
print_json_response::<FsActionResponse>(response)
|
||||
}
|
||||
FsCommand::Move(args) => {
|
||||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let body = FsMoveRequest {
|
||||
from: args.from.clone(),
|
||||
to: args.to.clone(),
|
||||
overwrite: if args.overwrite { Some(true) } else { None },
|
||||
};
|
||||
let response = ctx.post_with_query(
|
||||
&format!("{API_PREFIX}/fs/move"),
|
||||
&body,
|
||||
&[("session_id", args.session_id.clone())],
|
||||
)?;
|
||||
print_json_response::<FsMoveResponse>(response)
|
||||
}
|
||||
FsCommand::Stat(args) => {
|
||||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let response = ctx.get_with_query(
|
||||
&format!("{API_PREFIX}/fs/stat"),
|
||||
&[
|
||||
("path", Some(args.path.clone())),
|
||||
("session_id", args.session_id.clone()),
|
||||
],
|
||||
)?;
|
||||
print_json_response::<FsStat>(response)
|
||||
}
|
||||
FsCommand::UploadBatch(args) => {
|
||||
let ctx = ClientContext::new(cli, &args.client)?;
|
||||
let file = File::open(&args.tar_path)?;
|
||||
let response = ctx.post_raw_with_query(
|
||||
&format!("{API_PREFIX}/fs/upload-batch"),
|
||||
file,
|
||||
"application/x-tar",
|
||||
&[
|
||||
("path", args.path.clone()),
|
||||
("session_id", args.session_id.clone()),
|
||||
],
|
||||
)?;
|
||||
print_json_response::<FsUploadBatchResponse>(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_opencode_session(
|
||||
base_url: &str,
|
||||
token: Option<&str>,
|
||||
|
|
@ -1275,9 +1560,75 @@ impl ClientContext {
|
|||
Ok(request.send()?)
|
||||
}
|
||||
|
||||
fn put_raw_with_query<B: Into<reqwest::blocking::Body>>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: B,
|
||||
content_type: &str,
|
||||
query: &[(&str, Option<String>)],
|
||||
) -> Result<reqwest::blocking::Response, CliError> {
|
||||
let mut request = self
|
||||
.request(Method::PUT, path)
|
||||
.header(reqwest::header::CONTENT_TYPE, content_type)
|
||||
.header(reqwest::header::ACCEPT, "application/json");
|
||||
for (key, value) in query {
|
||||
if let Some(value) = value {
|
||||
request = request.query(&[(key, value)]);
|
||||
}
|
||||
}
|
||||
Ok(request.body(body).send()?)
|
||||
}
|
||||
|
||||
fn post_empty(&self, path: &str) -> Result<reqwest::blocking::Response, CliError> {
|
||||
Ok(self.request(Method::POST, path).send()?)
|
||||
}
|
||||
|
||||
fn post_empty_with_query(
|
||||
&self,
|
||||
path: &str,
|
||||
query: &[(&str, Option<String>)],
|
||||
) -> Result<reqwest::blocking::Response, CliError> {
|
||||
let mut request = self.request(Method::POST, path);
|
||||
for (key, value) in query {
|
||||
if let Some(value) = value {
|
||||
request = request.query(&[(key, value)]);
|
||||
}
|
||||
}
|
||||
Ok(request.send()?)
|
||||
}
|
||||
|
||||
fn delete_with_query(
|
||||
&self,
|
||||
path: &str,
|
||||
query: &[(&str, Option<String>)],
|
||||
) -> Result<reqwest::blocking::Response, CliError> {
|
||||
let mut request = self.request(Method::DELETE, path);
|
||||
for (key, value) in query {
|
||||
if let Some(value) = value {
|
||||
request = request.query(&[(key, value)]);
|
||||
}
|
||||
}
|
||||
Ok(request.send()?)
|
||||
}
|
||||
|
||||
fn post_raw_with_query<B: Into<reqwest::blocking::Body>>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: B,
|
||||
content_type: &str,
|
||||
query: &[(&str, Option<String>)],
|
||||
) -> Result<reqwest::blocking::Response, CliError> {
|
||||
let mut request = self
|
||||
.request(Method::POST, path)
|
||||
.header(reqwest::header::CONTENT_TYPE, content_type)
|
||||
.header(reqwest::header::ACCEPT, "application/json");
|
||||
for (key, value) in query {
|
||||
if let Some(value) = value {
|
||||
request = request.query(&[(key, value)]);
|
||||
}
|
||||
}
|
||||
Ok(request.body(body).send()?)
|
||||
}
|
||||
}
|
||||
|
||||
fn print_json_response<T: serde::de::DeserializeOwned + Serialize>(
|
||||
|
|
@ -1310,6 +1661,25 @@ fn print_text_response(response: reqwest::blocking::Response) -> Result<(), CliE
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn print_binary_response(response: reqwest::blocking::Response) -> Result<(), CliError> {
|
||||
let status = response.status();
|
||||
let bytes = response.bytes()?;
|
||||
|
||||
if !status.is_success() {
|
||||
if let Ok(text) = std::str::from_utf8(&bytes) {
|
||||
print_error_body(text)?;
|
||||
} else {
|
||||
write_stderr_line("Request failed with non-text response body")?;
|
||||
}
|
||||
return Err(CliError::HttpStatus(status));
|
||||
}
|
||||
|
||||
let mut out = std::io::stdout();
|
||||
out.write_all(&bytes)?;
|
||||
out.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_empty_response(response: reqwest::blocking::Response) -> Result<(), CliError> {
|
||||
let status = response.status();
|
||||
if status.is_success() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use sandbox_agent::cli::run_sandbox_agent;
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = sandbox_agent::cli::run_sandbox_agent() {
|
||||
if let Err(err) = run_sandbox_agent() {
|
||||
tracing::error!(error = %err, "sandbox-agent failed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -524,6 +524,8 @@ async fn ensure_backing_session(
|
|||
agent_version: None,
|
||||
directory,
|
||||
title,
|
||||
mcp: None,
|
||||
skills: None,
|
||||
};
|
||||
let manager = state.inner.session_manager();
|
||||
match manager
|
||||
|
|
@ -4264,7 +4266,7 @@ async fn oc_session_message_create(
|
|||
if let Err(err) = state
|
||||
.inner
|
||||
.session_manager()
|
||||
.send_message(session_id.clone(), prompt_text)
|
||||
.send_message(session_id.clone(), prompt_text, Vec::new())
|
||||
.await
|
||||
{
|
||||
let mut should_emit_idle = false;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -186,3 +186,130 @@ async fn agent_endpoints_snapshots() {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn create_session_with_skill_sources() {
|
||||
let app = TestApp::new();
|
||||
|
||||
// Create a temp skill directory with SKILL.md
|
||||
let skill_dir = tempfile::tempdir().expect("create skill dir");
|
||||
let skill_path = skill_dir.path().join("my-test-skill");
|
||||
std::fs::create_dir_all(&skill_path).expect("create skill subdir");
|
||||
std::fs::write(skill_path.join("SKILL.md"), "# Test Skill\nA test skill.").expect("write SKILL.md");
|
||||
|
||||
// Create session with local skill source
|
||||
let (status, payload) = send_json(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/sessions/skill-test-session",
|
||||
Some(json!({
|
||||
"agent": "mock",
|
||||
"skills": {
|
||||
"sources": [
|
||||
{
|
||||
"type": "local",
|
||||
"source": skill_dir.path().to_string_lossy()
|
||||
}
|
||||
]
|
||||
}
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "create session with skills: {payload}");
|
||||
assert!(
|
||||
payload.get("healthy").and_then(Value::as_bool).unwrap_or(false),
|
||||
"session should be healthy"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn create_session_with_skill_sources_filter() {
|
||||
let app = TestApp::new();
|
||||
|
||||
// Create a temp directory with two skills
|
||||
let skill_dir = tempfile::tempdir().expect("create skill dir");
|
||||
let wanted = skill_dir.path().join("wanted-skill");
|
||||
let unwanted = skill_dir.path().join("unwanted-skill");
|
||||
std::fs::create_dir_all(&wanted).expect("create wanted dir");
|
||||
std::fs::create_dir_all(&unwanted).expect("create unwanted dir");
|
||||
std::fs::write(wanted.join("SKILL.md"), "# Wanted").expect("write wanted SKILL.md");
|
||||
std::fs::write(unwanted.join("SKILL.md"), "# Unwanted").expect("write unwanted SKILL.md");
|
||||
|
||||
// Create session with filter
|
||||
let (status, payload) = send_json(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/sessions/skill-filter-session",
|
||||
Some(json!({
|
||||
"agent": "mock",
|
||||
"skills": {
|
||||
"sources": [
|
||||
{
|
||||
"type": "local",
|
||||
"source": skill_dir.path().to_string_lossy(),
|
||||
"skills": ["wanted-skill"]
|
||||
}
|
||||
]
|
||||
}
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "create session with skill filter: {payload}");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn create_session_with_invalid_skill_source() {
|
||||
let app = TestApp::new();
|
||||
|
||||
// Use a non-existent path
|
||||
let (status, _payload) = send_json(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/sessions/skill-invalid-session",
|
||||
Some(json!({
|
||||
"agent": "mock",
|
||||
"skills": {
|
||||
"sources": [
|
||||
{
|
||||
"type": "local",
|
||||
"source": "/nonexistent/path/to/skills"
|
||||
}
|
||||
]
|
||||
}
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
// Should fail with a 4xx or 5xx error
|
||||
assert_ne!(status, StatusCode::OK, "session with invalid skill source should fail");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn create_session_with_skill_filter_no_match() {
|
||||
let app = TestApp::new();
|
||||
|
||||
let skill_dir = tempfile::tempdir().expect("create skill dir");
|
||||
let skill_path = skill_dir.path().join("alpha");
|
||||
std::fs::create_dir_all(&skill_path).expect("create alpha dir");
|
||||
std::fs::write(skill_path.join("SKILL.md"), "# Alpha").expect("write SKILL.md");
|
||||
|
||||
// Filter for a skill that doesn't exist
|
||||
let (status, _payload) = send_json(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/sessions/skill-nomatch-session",
|
||||
Some(json!({
|
||||
"agent": "mock",
|
||||
"skills": {
|
||||
"sources": [
|
||||
{
|
||||
"type": "local",
|
||||
"source": skill_dir.path().to_string_lossy(),
|
||||
"skills": ["nonexistent"]
|
||||
}
|
||||
]
|
||||
}
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
assert_ne!(status, StatusCode::OK, "session with no matching skills should fail");
|
||||
}
|
||||
|
|
|
|||
267
server/packages/sandbox-agent/tests/http/fs_endpoints.rs
Normal file
267
server/packages/sandbox-agent/tests/http/fs_endpoints.rs
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
// Filesystem HTTP endpoints.
|
||||
include!("../common/http.rs");
|
||||
|
||||
use std::fs as stdfs;
|
||||
|
||||
use tar::{Builder, Header};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn fs_read_write_move_delete() {
|
||||
let app = TestApp::new();
|
||||
let cwd = std::env::current_dir().expect("cwd");
|
||||
let temp = tempfile::tempdir_in(&cwd).expect("tempdir");
|
||||
|
||||
let dir_path = temp.path();
|
||||
let file_path = dir_path.join("hello.txt");
|
||||
let file_path_str = file_path.to_string_lossy().to_string();
|
||||
|
||||
let request = Request::builder()
|
||||
.method(Method::PUT)
|
||||
.uri(format!("/v1/fs/file?path={file_path_str}"))
|
||||
.header(header::CONTENT_TYPE, "application/octet-stream")
|
||||
.body(Body::from("hello"))
|
||||
.expect("write request");
|
||||
let (status, _headers, _payload) = send_json_request(&app.app, request).await;
|
||||
assert_eq!(status, StatusCode::OK, "write file");
|
||||
|
||||
let request = Request::builder()
|
||||
.method(Method::GET)
|
||||
.uri(format!("/v1/fs/file?path={file_path_str}"))
|
||||
.body(Body::empty())
|
||||
.expect("read request");
|
||||
let (status, headers, bytes) = send_request(&app.app, request).await;
|
||||
assert_eq!(status, StatusCode::OK, "read file");
|
||||
assert_eq!(
|
||||
headers
|
||||
.get(header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("application/octet-stream")
|
||||
);
|
||||
assert_eq!(bytes.as_ref(), b"hello");
|
||||
|
||||
let entries_path = dir_path.to_string_lossy().to_string();
|
||||
let (status, entries) = send_json(
|
||||
&app.app,
|
||||
Method::GET,
|
||||
&format!("/v1/fs/entries?path={entries_path}"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "list entries");
|
||||
let entry_list = entries.as_array().cloned().unwrap_or_default();
|
||||
let entry_names: Vec<String> = entry_list
|
||||
.iter()
|
||||
.filter_map(|entry| entry.get("name").and_then(|value| value.as_str()))
|
||||
.map(|value| value.to_string())
|
||||
.collect();
|
||||
assert!(entry_names.contains(&"hello.txt".to_string()));
|
||||
|
||||
let new_path = dir_path.join("moved.txt");
|
||||
let new_path_str = new_path.to_string_lossy().to_string();
|
||||
let (status, _payload) = send_json(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
"/v1/fs/move",
|
||||
Some(json!({
|
||||
"from": file_path_str,
|
||||
"to": new_path_str,
|
||||
"overwrite": true
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "move file");
|
||||
assert!(new_path.exists(), "moved file exists");
|
||||
|
||||
let (status, _payload) = send_json(
|
||||
&app.app,
|
||||
Method::DELETE,
|
||||
&format!("/v1/fs/entry?path={}", new_path.to_string_lossy()),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "delete file");
|
||||
assert!(!new_path.exists(), "file deleted");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn fs_upload_batch_tar() {
|
||||
let app = TestApp::new();
|
||||
let cwd = std::env::current_dir().expect("cwd");
|
||||
let dest_dir = tempfile::tempdir_in(&cwd).expect("tempdir");
|
||||
|
||||
let mut builder = Builder::new(Vec::new());
|
||||
let mut tar_header = Header::new_gnu();
|
||||
let contents = b"hello";
|
||||
tar_header.set_size(contents.len() as u64);
|
||||
tar_header.set_cksum();
|
||||
builder
|
||||
.append_data(&mut tar_header, "a.txt", &contents[..])
|
||||
.expect("append tar entry");
|
||||
|
||||
let mut tar_header = Header::new_gnu();
|
||||
let contents = b"world";
|
||||
tar_header.set_size(contents.len() as u64);
|
||||
tar_header.set_cksum();
|
||||
builder
|
||||
.append_data(&mut tar_header, "nested/b.txt", &contents[..])
|
||||
.expect("append tar entry");
|
||||
|
||||
let tar_bytes = builder.into_inner().expect("tar bytes");
|
||||
|
||||
let request = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(format!(
|
||||
"/v1/fs/upload-batch?path={}",
|
||||
dest_dir.path().to_string_lossy()
|
||||
))
|
||||
.header(header::CONTENT_TYPE, "application/x-tar")
|
||||
.body(Body::from(tar_bytes))
|
||||
.expect("tar request");
|
||||
|
||||
let (status, _headers, payload) = send_json_request(&app.app, request).await;
|
||||
assert_eq!(status, StatusCode::OK, "upload batch");
|
||||
assert!(payload
|
||||
.get("paths")
|
||||
.and_then(|value| value.as_array())
|
||||
.map(|value| !value.is_empty())
|
||||
.unwrap_or(false));
|
||||
assert!(payload.get("truncated").and_then(|value| value.as_bool()) == Some(false));
|
||||
|
||||
let a_path = dest_dir.path().join("a.txt");
|
||||
let b_path = dest_dir.path().join("nested").join("b.txt");
|
||||
assert!(a_path.exists(), "a.txt extracted");
|
||||
assert!(b_path.exists(), "b.txt extracted");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn fs_relative_paths_use_session_dir() {
|
||||
let app = TestApp::new();
|
||||
|
||||
let session_id = "fs-session";
|
||||
let status = send_status(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
&format!("/v1/sessions/{session_id}"),
|
||||
Some(json!({ "agent": "mock" })),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "create session");
|
||||
|
||||
let cwd = std::env::current_dir().expect("cwd");
|
||||
let temp = tempfile::tempdir_in(&cwd).expect("tempdir");
|
||||
let relative_dir = temp
|
||||
.path()
|
||||
.strip_prefix(&cwd)
|
||||
.expect("strip prefix")
|
||||
.to_path_buf();
|
||||
let relative_path = relative_dir.join("session.txt");
|
||||
|
||||
let request = Request::builder()
|
||||
.method(Method::PUT)
|
||||
.uri(format!(
|
||||
"/v1/fs/file?session_id={session_id}&path={}",
|
||||
relative_path.to_string_lossy()
|
||||
))
|
||||
.header(header::CONTENT_TYPE, "application/octet-stream")
|
||||
.body(Body::from("session"))
|
||||
.expect("write request");
|
||||
let (status, _headers, _payload) = send_json_request(&app.app, request).await;
|
||||
assert_eq!(status, StatusCode::OK, "write relative file");
|
||||
|
||||
let absolute_path = cwd.join(relative_path);
|
||||
let content = stdfs::read_to_string(&absolute_path).expect("read file");
|
||||
assert_eq!(content, "session");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn fs_upload_batch_truncates_paths() {
|
||||
let app = TestApp::new();
|
||||
let cwd = std::env::current_dir().expect("cwd");
|
||||
let dest_dir = tempfile::tempdir_in(&cwd).expect("tempdir");
|
||||
|
||||
let mut builder = Builder::new(Vec::new());
|
||||
for index in 0..1030 {
|
||||
let mut tar_header = Header::new_gnu();
|
||||
tar_header.set_size(0);
|
||||
tar_header.set_cksum();
|
||||
let name = format!("file_{index}.txt");
|
||||
builder
|
||||
.append_data(&mut tar_header, name, &[][..])
|
||||
.expect("append tar entry");
|
||||
}
|
||||
let tar_bytes = builder.into_inner().expect("tar bytes");
|
||||
|
||||
let request = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(format!(
|
||||
"/v1/fs/upload-batch?path={}",
|
||||
dest_dir.path().to_string_lossy()
|
||||
))
|
||||
.header(header::CONTENT_TYPE, "application/x-tar")
|
||||
.body(Body::from(tar_bytes))
|
||||
.expect("tar request");
|
||||
|
||||
let (status, _headers, payload) = send_json_request(&app.app, request).await;
|
||||
assert_eq!(status, StatusCode::OK, "upload batch");
|
||||
let paths = payload
|
||||
.get("paths")
|
||||
.and_then(|value| value.as_array())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
assert_eq!(paths.len(), 1024);
|
||||
assert_eq!(payload.get("truncated").and_then(|value| value.as_bool()), Some(true));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn fs_mkdir_stat_and_delete_directory() {
|
||||
let app = TestApp::new();
|
||||
let cwd = std::env::current_dir().expect("cwd");
|
||||
let temp = tempfile::tempdir_in(&cwd).expect("tempdir");
|
||||
|
||||
let dir_path = temp.path().join("nested");
|
||||
let dir_path_str = dir_path.to_string_lossy().to_string();
|
||||
|
||||
let status = send_status(
|
||||
&app.app,
|
||||
Method::POST,
|
||||
&format!("/v1/fs/mkdir?path={dir_path_str}"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "mkdir");
|
||||
assert!(dir_path.exists(), "directory created");
|
||||
|
||||
let (status, stat) = send_json(
|
||||
&app.app,
|
||||
Method::GET,
|
||||
&format!("/v1/fs/stat?path={dir_path_str}"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "stat directory");
|
||||
assert_eq!(stat["entryType"], "directory");
|
||||
|
||||
let file_path = dir_path.join("note.txt");
|
||||
stdfs::write(&file_path, "content").expect("write file");
|
||||
let file_path_str = file_path.to_string_lossy().to_string();
|
||||
|
||||
let (status, stat) = send_json(
|
||||
&app.app,
|
||||
Method::GET,
|
||||
&format!("/v1/fs/stat?path={file_path_str}"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "stat file");
|
||||
assert_eq!(stat["entryType"], "file");
|
||||
|
||||
let status = send_status(
|
||||
&app.app,
|
||||
Method::DELETE,
|
||||
&format!("/v1/fs/entry?path={dir_path_str}&recursive=true"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "delete directory");
|
||||
assert!(!dir_path.exists(), "directory deleted");
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 145
|
||||
expression: snapshot_status(status)
|
||||
---
|
||||
status: 204
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
expression: snapshot_status(status)
|
||||
---
|
||||
status: 204
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 185
|
||||
expression: "normalize_agent_models(&models, config.agent)"
|
||||
---
|
||||
defaultInList: true
|
||||
defaultModel: amp-default
|
||||
hasDefault: true
|
||||
hasVariants: false
|
||||
ids:
|
||||
- amp-default
|
||||
modelCount: 1
|
||||
nonEmpty: true
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 185
|
||||
expression: "normalize_agent_models(&models, config.agent)"
|
||||
---
|
||||
defaultInList: true
|
||||
hasDefault: true
|
||||
hasVariants: "<redacted>"
|
||||
nonEmpty: true
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 185
|
||||
expression: "normalize_agent_models(&models, config.agent)"
|
||||
---
|
||||
defaultInList: true
|
||||
hasDefault: true
|
||||
hasVariants: false
|
||||
nonEmpty: true
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
expression: "normalize_agent_models(&models, config.agent)"
|
||||
---
|
||||
defaultInList: true
|
||||
hasDefault: true
|
||||
hasVariants: "<redacted>"
|
||||
nonEmpty: true
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 162
|
||||
expression: normalize_agent_modes(&modes)
|
||||
---
|
||||
modes:
|
||||
- description: true
|
||||
id: build
|
||||
name: Build
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
assertion_line: 162
|
||||
expression: normalize_agent_modes(&modes)
|
||||
---
|
||||
modes:
|
||||
- description: true
|
||||
id: build
|
||||
name: Build
|
||||
- description: true
|
||||
id: plan
|
||||
name: Plan
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
|
||||
expression: normalize_agent_modes(&modes)
|
||||
---
|
||||
modes:
|
||||
- description: true
|
||||
id: build
|
||||
name: Build
|
||||
- description: true
|
||||
id: custom
|
||||
name: Custom
|
||||
- description: true
|
||||
id: plan
|
||||
name: Plan
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
#[path = "http/agent_endpoints.rs"]
|
||||
mod agent_endpoints;
|
||||
#[path = "http/fs_endpoints.rs"]
|
||||
mod fs_endpoints;
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/sessions/multi_turn.rs
|
||||
assertion_line: 15
|
||||
expression: value
|
||||
---
|
||||
first:
|
||||
- metadata: true
|
||||
seq: 1
|
||||
session: started
|
||||
type: session.started
|
||||
- seq: 2
|
||||
type: turn.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: in_progress
|
||||
seq: 3
|
||||
type: item.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: completed
|
||||
seq: 4
|
||||
type: item.completed
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: assistant
|
||||
status: in_progress
|
||||
seq: 5
|
||||
type: item.started
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 6
|
||||
type: item.delta
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 7
|
||||
type: item.delta
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 8
|
||||
type: item.delta
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 9
|
||||
type: item.delta
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 10
|
||||
type: item.delta
|
||||
second:
|
||||
- seq: 1
|
||||
type: turn.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: assistant
|
||||
status: completed
|
||||
seq: 2
|
||||
type: item.completed
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/sessions/permissions.rs
|
||||
assertion_line: 12
|
||||
expression: value
|
||||
---
|
||||
- metadata: true
|
||||
seq: 1
|
||||
session: started
|
||||
type: session.started
|
||||
- seq: 2
|
||||
type: turn.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: in_progress
|
||||
seq: 3
|
||||
type: item.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: completed
|
||||
seq: 4
|
||||
type: item.completed
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: assistant
|
||||
status: in_progress
|
||||
seq: 5
|
||||
type: item.started
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 6
|
||||
type: item.delta
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 7
|
||||
type: item.delta
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 8
|
||||
type: item.delta
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 9
|
||||
type: item.delta
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 10
|
||||
type: item.delta
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/sessions/questions.rs
|
||||
assertion_line: 12
|
||||
expression: value
|
||||
---
|
||||
- metadata: true
|
||||
seq: 1
|
||||
session: started
|
||||
type: session.started
|
||||
- seq: 2
|
||||
type: turn.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: in_progress
|
||||
seq: 3
|
||||
type: item.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: completed
|
||||
seq: 4
|
||||
type: item.completed
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: assistant
|
||||
status: in_progress
|
||||
seq: 5
|
||||
type: item.started
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 6
|
||||
type: item.delta
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: assistant
|
||||
status: completed
|
||||
seq: 7
|
||||
type: item.completed
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/sessions/session_lifecycle.rs
|
||||
assertion_line: 12
|
||||
expression: value
|
||||
---
|
||||
session_a:
|
||||
- metadata: true
|
||||
seq: 1
|
||||
session: started
|
||||
type: session.started
|
||||
- seq: 2
|
||||
type: turn.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: in_progress
|
||||
seq: 3
|
||||
type: item.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: completed
|
||||
seq: 4
|
||||
type: item.completed
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: assistant
|
||||
status: in_progress
|
||||
seq: 5
|
||||
type: item.started
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 6
|
||||
type: item.delta
|
||||
session_b:
|
||||
- metadata: true
|
||||
seq: 1
|
||||
session: started
|
||||
type: session.started
|
||||
- seq: 2
|
||||
type: turn.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: in_progress
|
||||
seq: 3
|
||||
type: item.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: completed
|
||||
seq: 4
|
||||
type: item.completed
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: assistant
|
||||
status: in_progress
|
||||
seq: 5
|
||||
type: item.started
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 6
|
||||
type: item.delta
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/sessions/session_lifecycle.rs
|
||||
assertion_line: 12
|
||||
expression: value
|
||||
---
|
||||
healthy: true
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/sessions/session_lifecycle.rs
|
||||
expression: value
|
||||
---
|
||||
hasExpectedFields: true
|
||||
sessionCount: 1
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
---
|
||||
source: server/packages/sandbox-agent/tests/sessions/../common/http.rs
|
||||
assertion_line: 1001
|
||||
expression: normalized
|
||||
---
|
||||
- metadata: true
|
||||
seq: 1
|
||||
session: started
|
||||
type: session.started
|
||||
- seq: 2
|
||||
type: turn.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: in_progress
|
||||
seq: 3
|
||||
type: item.started
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: user
|
||||
status: completed
|
||||
seq: 4
|
||||
type: item.completed
|
||||
- item:
|
||||
content_types:
|
||||
- text
|
||||
kind: message
|
||||
role: assistant
|
||||
status: in_progress
|
||||
seq: 5
|
||||
type: item.started
|
||||
- delta:
|
||||
delta: "<redacted>"
|
||||
item_id: "<redacted>"
|
||||
native_item_id: "<redacted>"
|
||||
seq: 6
|
||||
type: item.delta
|
||||
Loading…
Add table
Add a link
Reference in a new issue