mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Add subagent identity to hook inputs
This commit is contained in:
@@ -10,6 +10,12 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"agent_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"agent_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"agent_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"agent_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"agent_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"agent_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -224,6 +224,7 @@ with Path(r"{log_path}").open("a", encoding="utf-8") as handle:
|
||||
let preview = engine.preview_pre_tool_use(&PreToolUseRequest {
|
||||
session_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd: cwd.clone(),
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
@@ -240,6 +241,7 @@ with Path(r"{log_path}").open("a", encoding="utf-8") as handle:
|
||||
.run_pre_tool_use(PreToolUseRequest {
|
||||
session_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd,
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
@@ -311,6 +313,7 @@ async fn requirements_managed_hooks_execute_windows_command_override() {
|
||||
.run_pre_tool_use(PreToolUseRequest {
|
||||
session_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd: cwd(),
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
@@ -696,6 +699,7 @@ fn requirements_managed_hooks_load_when_managed_dir_is_missing() {
|
||||
let preview = engine.preview_pre_tool_use(&PreToolUseRequest {
|
||||
session_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd,
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
@@ -1101,6 +1105,7 @@ fn discovers_hooks_from_json_and_toml_in_the_same_layer() {
|
||||
let preview = engine.preview_pre_tool_use(&PreToolUseRequest {
|
||||
session_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd,
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
@@ -1186,6 +1191,7 @@ print(json.dumps({
|
||||
let preview = engine.preview_pre_tool_use(&PreToolUseRequest {
|
||||
session_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd: cwd(),
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
@@ -1217,6 +1223,7 @@ print(json.dumps({
|
||||
.run_pre_tool_use(PreToolUseRequest {
|
||||
session_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd: cwd(),
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
|
||||
@@ -8,6 +8,13 @@ use codex_protocol::protocol::HookRunSummary;
|
||||
use crate::engine::ConfiguredHandler;
|
||||
use crate::engine::dispatcher;
|
||||
|
||||
/// Identifies a thread-spawned subagent when a normal hook runs inside it.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SubagentHookContext {
|
||||
pub agent_id: String,
|
||||
pub agent_type: String,
|
||||
}
|
||||
|
||||
pub(crate) fn join_text_chunks(chunks: Vec<String>) -> Option<String> {
|
||||
if chunks.is_empty() {
|
||||
None
|
||||
|
||||
@@ -17,11 +17,13 @@ use crate::engine::dispatcher;
|
||||
use crate::engine::output_parser;
|
||||
use crate::schema::PostCompactCommandInput;
|
||||
use crate::schema::PreCompactCommandInput;
|
||||
use crate::schema::SubagentCommandInputFields;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PreCompactRequest {
|
||||
pub session_id: ThreadId,
|
||||
pub turn_id: String,
|
||||
pub subagent: Option<common::SubagentHookContext>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub transcript_path: Option<PathBuf>,
|
||||
pub model: String,
|
||||
@@ -32,6 +34,7 @@ pub struct PreCompactRequest {
|
||||
pub struct PostCompactRequest {
|
||||
pub session_id: ThreadId,
|
||||
pub turn_id: String,
|
||||
pub subagent: Option<common::SubagentHookContext>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub transcript_path: Option<PathBuf>,
|
||||
pub model: String,
|
||||
@@ -120,9 +123,12 @@ pub(crate) async fn run_pre(
|
||||
}
|
||||
|
||||
fn pre_command_input_json(request: &PreCompactRequest) -> Result<String, serde_json::Error> {
|
||||
let subagent = SubagentCommandInputFields::from(request.subagent.as_ref());
|
||||
serde_json::to_string(&PreCompactCommandInput {
|
||||
session_id: request.session_id.to_string(),
|
||||
turn_id: request.turn_id.clone(),
|
||||
agent_id: subagent.agent_id,
|
||||
agent_type: subagent.agent_type,
|
||||
transcript_path: crate::schema::NullableString::from_path(request.transcript_path.clone()),
|
||||
cwd: request.cwd.display().to_string(),
|
||||
hook_event_name: "PreCompact".to_string(),
|
||||
@@ -199,9 +205,12 @@ pub(crate) async fn run_post(
|
||||
}
|
||||
|
||||
fn post_command_input_json(request: &PostCompactRequest) -> Result<String, serde_json::Error> {
|
||||
let subagent = SubagentCommandInputFields::from(request.subagent.as_ref());
|
||||
serde_json::to_string(&PostCompactCommandInput {
|
||||
session_id: request.session_id.to_string(),
|
||||
turn_id: request.turn_id.clone(),
|
||||
agent_id: subagent.agent_id,
|
||||
agent_type: subagent.agent_type,
|
||||
transcript_path: crate::schema::NullableString::from_path(request.transcript_path.clone()),
|
||||
cwd: request.cwd.display().to_string(),
|
||||
hook_event_name: "PostCompact".to_string(),
|
||||
@@ -563,6 +572,7 @@ mod tests {
|
||||
session_id: ThreadId::from_string("00000000-0000-4000-8000-000000000001")
|
||||
.expect("valid thread id"),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
@@ -575,6 +585,7 @@ mod tests {
|
||||
session_id: ThreadId::from_string("00000000-0000-4000-8000-000000000002")
|
||||
.expect("valid thread id"),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::engine::command_runner::CommandRunResult;
|
||||
use crate::engine::dispatcher;
|
||||
use crate::engine::output_parser;
|
||||
use crate::schema::PermissionRequestCommandInput;
|
||||
use crate::schema::SubagentCommandInputFields;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::HookCompletedEvent;
|
||||
use codex_protocol::protocol::HookEventName;
|
||||
@@ -35,6 +36,7 @@ use serde_json::Value;
|
||||
pub struct PermissionRequestRequest {
|
||||
pub session_id: ThreadId,
|
||||
pub turn_id: String,
|
||||
pub subagent: Option<common::SubagentHookContext>,
|
||||
pub cwd: PathBuf,
|
||||
pub transcript_path: Option<PathBuf>,
|
||||
pub model: String,
|
||||
@@ -168,9 +170,12 @@ fn resolve_permission_request_decision<'a>(
|
||||
}
|
||||
|
||||
fn build_command_input(request: &PermissionRequestRequest) -> PermissionRequestCommandInput {
|
||||
let subagent = SubagentCommandInputFields::from(request.subagent.as_ref());
|
||||
PermissionRequestCommandInput {
|
||||
session_id: request.session_id.to_string(),
|
||||
turn_id: request.turn_id.clone(),
|
||||
agent_id: subagent.agent_id,
|
||||
agent_type: subagent.agent_type,
|
||||
transcript_path: crate::schema::NullableString::from_path(request.transcript_path.clone()),
|
||||
cwd: request.cwd.display().to_string(),
|
||||
hook_event_name: "PermissionRequest".to_string(),
|
||||
|
||||
@@ -17,11 +17,13 @@ use crate::engine::command_runner::CommandRunResult;
|
||||
use crate::engine::dispatcher;
|
||||
use crate::engine::output_parser;
|
||||
use crate::schema::PostToolUseCommandInput;
|
||||
use crate::schema::SubagentCommandInputFields;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PostToolUseRequest {
|
||||
pub session_id: ThreadId,
|
||||
pub turn_id: String,
|
||||
pub subagent: Option<common::SubagentHookContext>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub transcript_path: Option<PathBuf>,
|
||||
pub model: String,
|
||||
@@ -148,9 +150,12 @@ pub(crate) async fn run(
|
||||
/// events across processes. Shell-like tools pass `{ "command": ... }` as
|
||||
/// `tool_input`; MCP tools pass their resolved JSON arguments.
|
||||
fn command_input_json(request: &PostToolUseRequest) -> Result<String, serde_json::Error> {
|
||||
let subagent = SubagentCommandInputFields::from(request.subagent.as_ref());
|
||||
serde_json::to_string(&PostToolUseCommandInput {
|
||||
session_id: request.session_id.to_string(),
|
||||
turn_id: request.turn_id.clone(),
|
||||
agent_id: subagent.agent_id,
|
||||
agent_type: subagent.agent_type,
|
||||
transcript_path: crate::schema::NullableString::from_path(request.transcript_path.clone()),
|
||||
cwd: request.cwd.display().to_string(),
|
||||
hook_event_name: "PostToolUse".to_string(),
|
||||
@@ -571,6 +576,7 @@ mod tests {
|
||||
super::PostToolUseRequest {
|
||||
session_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
|
||||
@@ -17,11 +17,13 @@ use crate::engine::command_runner::CommandRunResult;
|
||||
use crate::engine::dispatcher;
|
||||
use crate::engine::output_parser;
|
||||
use crate::schema::PreToolUseCommandInput;
|
||||
use crate::schema::SubagentCommandInputFields;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PreToolUseRequest {
|
||||
pub session_id: ThreadId,
|
||||
pub turn_id: String,
|
||||
pub subagent: Option<common::SubagentHookContext>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub transcript_path: Option<PathBuf>,
|
||||
pub model: String,
|
||||
@@ -166,9 +168,12 @@ fn latest_updated_input(
|
||||
/// stable. Shell-like tools pass `{ "command": ... }` as `tool_input`; MCP
|
||||
/// tools pass their resolved JSON arguments.
|
||||
fn command_input_json(request: &PreToolUseRequest) -> Result<String, serde_json::Error> {
|
||||
let subagent = SubagentCommandInputFields::from(request.subagent.as_ref());
|
||||
serde_json::to_string(&PreToolUseCommandInput {
|
||||
session_id: request.session_id.to_string(),
|
||||
turn_id: request.turn_id.clone(),
|
||||
agent_id: subagent.agent_id,
|
||||
agent_type: subagent.agent_type,
|
||||
transcript_path: crate::schema::NullableString::from_path(request.transcript_path.clone()),
|
||||
cwd: request.cwd.display().to_string(),
|
||||
hook_event_name: "PreToolUse".to_string(),
|
||||
@@ -763,6 +768,7 @@ mod tests {
|
||||
super::PreToolUseRequest {
|
||||
session_id: ThreadId::new(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
subagent: None,
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
transcript_path: None,
|
||||
model: "gpt-test".to_string(),
|
||||
|
||||
@@ -16,12 +16,14 @@ use crate::engine::command_runner::CommandRunResult;
|
||||
use crate::engine::dispatcher;
|
||||
use crate::engine::output_parser;
|
||||
use crate::schema::NullableString;
|
||||
use crate::schema::SubagentCommandInputFields;
|
||||
use crate::schema::UserPromptSubmitCommandInput;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserPromptSubmitRequest {
|
||||
pub session_id: ThreadId,
|
||||
pub turn_id: String,
|
||||
pub subagent: Option<common::SubagentHookContext>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub transcript_path: Option<PathBuf>,
|
||||
pub model: String,
|
||||
@@ -77,9 +79,12 @@ pub(crate) async fn run(
|
||||
};
|
||||
}
|
||||
|
||||
let subagent = SubagentCommandInputFields::from(request.subagent.as_ref());
|
||||
let input_json = match serde_json::to_string(&UserPromptSubmitCommandInput {
|
||||
session_id: request.session_id.to_string(),
|
||||
turn_id: request.turn_id.clone(),
|
||||
agent_id: subagent.agent_id,
|
||||
agent_type: subagent.agent_type,
|
||||
transcript_path: NullableString::from_path(request.transcript_path.clone()),
|
||||
cwd: request.cwd.display().to_string(),
|
||||
hook_event_name: "UserPromptSubmit".to_string(),
|
||||
|
||||
@@ -14,6 +14,7 @@ pub use config_rules::hook_states_from_stack;
|
||||
pub use declarations::PluginHookDeclaration;
|
||||
pub use declarations::plugin_hook_declarations;
|
||||
pub use engine::HookListEntry;
|
||||
pub use events::common::SubagentHookContext;
|
||||
/// Hook event names as they appear in hooks JSON and config files.
|
||||
pub const HOOK_EVENT_NAMES: [&str; 10] = [
|
||||
"PreToolUse",
|
||||
|
||||
@@ -12,6 +12,8 @@ use serde_json::Value;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::events::common::SubagentHookContext;
|
||||
|
||||
const GENERATED_DIR: &str = "generated";
|
||||
const POST_TOOL_USE_INPUT_FIXTURE: &str = "post-tool-use.command.input.schema.json";
|
||||
const POST_TOOL_USE_OUTPUT_FIXTURE: &str = "post-tool-use.command.output.schema.json";
|
||||
@@ -61,6 +63,24 @@ impl JsonSchema for NullableString {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct SubagentCommandInputFields {
|
||||
pub agent_id: Option<String>,
|
||||
pub agent_type: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Option<&SubagentHookContext>> for SubagentCommandInputFields {
|
||||
fn from(value: Option<&SubagentHookContext>) -> Self {
|
||||
match value {
|
||||
Some(context) => Self {
|
||||
agent_id: Some(context.agent_id.clone()),
|
||||
agent_type: Some(context.agent_type.clone()),
|
||||
},
|
||||
None => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -251,6 +271,10 @@ pub(crate) struct PreToolUseCommandInput {
|
||||
pub session_id: String,
|
||||
/// Codex extension: expose the active turn id to internal turn-scoped hooks.
|
||||
pub turn_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_type: Option<String>,
|
||||
pub transcript_path: NullableString,
|
||||
pub cwd: String,
|
||||
#[schemars(schema_with = "pre_tool_use_hook_event_name_schema")]
|
||||
@@ -270,6 +294,10 @@ pub(crate) struct PermissionRequestCommandInput {
|
||||
pub session_id: String,
|
||||
/// Codex extension: expose the active turn id to internal turn-scoped hooks.
|
||||
pub turn_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_type: Option<String>,
|
||||
pub transcript_path: NullableString,
|
||||
pub cwd: String,
|
||||
#[schemars(schema_with = "permission_request_hook_event_name_schema")]
|
||||
@@ -288,6 +316,10 @@ pub(crate) struct PostToolUseCommandInput {
|
||||
pub session_id: String,
|
||||
/// Codex extension: expose the active turn id to internal turn-scoped hooks.
|
||||
pub turn_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_type: Option<String>,
|
||||
pub transcript_path: NullableString,
|
||||
pub cwd: String,
|
||||
#[schemars(schema_with = "post_tool_use_hook_event_name_schema")]
|
||||
@@ -308,6 +340,10 @@ pub(crate) struct PreCompactCommandInput {
|
||||
pub session_id: String,
|
||||
/// Codex extension: expose the active turn id to internal turn-scoped hooks.
|
||||
pub turn_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_type: Option<String>,
|
||||
pub transcript_path: NullableString,
|
||||
pub cwd: String,
|
||||
#[schemars(schema_with = "pre_compact_hook_event_name_schema")]
|
||||
@@ -324,6 +360,10 @@ pub(crate) struct PostCompactCommandInput {
|
||||
pub session_id: String,
|
||||
/// Codex extension: expose the active turn id to internal turn-scoped hooks.
|
||||
pub turn_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_type: Option<String>,
|
||||
pub transcript_path: NullableString,
|
||||
pub cwd: String,
|
||||
#[schemars(schema_with = "post_compact_hook_event_name_schema")]
|
||||
@@ -486,6 +526,10 @@ pub(crate) struct UserPromptSubmitCommandInput {
|
||||
pub session_id: String,
|
||||
/// Codex extension: expose the active turn id to internal turn-scoped hooks.
|
||||
pub turn_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_type: Option<String>,
|
||||
pub transcript_path: NullableString,
|
||||
pub cwd: String,
|
||||
#[schemars(schema_with = "user_prompt_submit_hook_event_name_schema")]
|
||||
@@ -761,6 +805,7 @@ fn default_continue() -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::NullableString;
|
||||
use super::PERMISSION_REQUEST_INPUT_FIXTURE;
|
||||
use super::PERMISSION_REQUEST_OUTPUT_FIXTURE;
|
||||
use super::POST_COMPACT_INPUT_FIXTURE;
|
||||
@@ -785,6 +830,7 @@ mod tests {
|
||||
use super::SUBAGENT_STOP_INPUT_FIXTURE;
|
||||
use super::SUBAGENT_STOP_OUTPUT_FIXTURE;
|
||||
use super::StopCommandInput;
|
||||
use super::SubagentCommandInputFields;
|
||||
use super::SubagentStartCommandInput;
|
||||
use super::SubagentStopCommandInput;
|
||||
use super::USER_PROMPT_SUBMIT_INPUT_FIXTURE;
|
||||
@@ -792,8 +838,10 @@ mod tests {
|
||||
use super::UserPromptSubmitCommandInput;
|
||||
use super::schema_json;
|
||||
use super::write_schema_fixtures;
|
||||
use crate::events::common::SubagentHookContext;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn expected_fixture(name: &str) -> &'static str {
|
||||
@@ -968,4 +1016,87 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subagent_context_fields_are_optional_for_hooks_that_run_inside_subagents() {
|
||||
let schemas = [
|
||||
schema_json::<PreToolUseCommandInput>().expect("serialize pre tool use input schema"),
|
||||
schema_json::<PermissionRequestCommandInput>()
|
||||
.expect("serialize permission request input schema"),
|
||||
schema_json::<PostToolUseCommandInput>().expect("serialize post tool use input schema"),
|
||||
schema_json::<PreCompactCommandInput>().expect("serialize pre compact input schema"),
|
||||
schema_json::<PostCompactCommandInput>().expect("serialize post compact input schema"),
|
||||
schema_json::<UserPromptSubmitCommandInput>()
|
||||
.expect("serialize user prompt submit input schema"),
|
||||
];
|
||||
|
||||
for schema in schemas {
|
||||
let schema: Value = serde_json::from_slice(&schema).expect("parse hook input schema");
|
||||
assert_eq!(schema["properties"]["agent_id"]["type"], "string");
|
||||
assert_eq!(schema["properties"]["agent_type"]["type"], "string");
|
||||
let required = schema["required"]
|
||||
.as_array()
|
||||
.expect("schema required fields");
|
||||
assert!(!required.contains(&Value::String("agent_id".to_string())));
|
||||
assert!(!required.contains(&Value::String("agent_type".to_string())));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subagent_context_fields_serialize_flat_and_omit_when_absent() {
|
||||
let subagent = SubagentCommandInputFields::from(Some(&SubagentHookContext {
|
||||
agent_id: "agent-1".to_string(),
|
||||
agent_type: "worker".to_string(),
|
||||
}));
|
||||
let input = PreToolUseCommandInput {
|
||||
session_id: "session-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
agent_id: subagent.agent_id,
|
||||
agent_type: subagent.agent_type,
|
||||
transcript_path: NullableString::from_path(None),
|
||||
cwd: "/tmp".to_string(),
|
||||
hook_event_name: "PreToolUse".to_string(),
|
||||
model: "gpt-test".to_string(),
|
||||
permission_mode: "default".to_string(),
|
||||
tool_name: "Bash".to_string(),
|
||||
tool_input: json!({ "command": "echo hello" }),
|
||||
tool_use_id: "tool-1".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(input).expect("serialize subagent hook input"),
|
||||
json!({
|
||||
"session_id": "session-1",
|
||||
"turn_id": "turn-1",
|
||||
"agent_id": "agent-1",
|
||||
"agent_type": "worker",
|
||||
"transcript_path": null,
|
||||
"cwd": "/tmp",
|
||||
"hook_event_name": "PreToolUse",
|
||||
"model": "gpt-test",
|
||||
"permission_mode": "default",
|
||||
"tool_name": "Bash",
|
||||
"tool_input": { "command": "echo hello" },
|
||||
"tool_use_id": "tool-1",
|
||||
})
|
||||
);
|
||||
|
||||
let root_input = PreToolUseCommandInput {
|
||||
session_id: "session-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
agent_id: None,
|
||||
agent_type: None,
|
||||
transcript_path: NullableString::from_path(None),
|
||||
cwd: "/tmp".to_string(),
|
||||
hook_event_name: "PreToolUse".to_string(),
|
||||
model: "gpt-test".to_string(),
|
||||
permission_mode: "default".to_string(),
|
||||
tool_name: "Bash".to_string(),
|
||||
tool_input: json!({ "command": "echo hello" }),
|
||||
tool_use_id: "tool-1".to_string(),
|
||||
};
|
||||
let root_input = serde_json::to_value(root_input).expect("serialize root hook input");
|
||||
assert_eq!(root_input.get("agent_id"), None);
|
||||
assert_eq!(root_input.get("agent_type"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user