Codex Handbook
ext/skills/src/tools/read.rs 112 lines
use codex_extension_api::FunctionCallError;use codex_extension_api::ToolCall;use codex_extension_api::ToolExecutor;use codex_extension_api::ToolExecutorFuture;use codex_extension_api::ToolName;use codex_extension_api::ToolSpec;use schemars::JsonSchema;use serde::Deserialize;use serde::Serialize;use crate::catalog::SkillPackageId;use crate::catalog::SkillResourceId;use crate::provider::SkillReadRequest;use super::MAX_HANDLE_BYTES;use super::SkillToolAuthority;use super::SkillToolContext;use super::external_json_output;use super::parse_args;use super::skill_function_tool;use super::skill_tool_name;use super::validate_handle;const TOOL_NAME: &str = "read";#[derive(Deserialize, JsonSchema)]#[serde(deny_unknown_fields)]struct ReadArgs {    authority: SkillToolAuthority,    package: String,    resource: String,}#[derive(Debug, Eq, JsonSchema, PartialEq, Serialize)]#[schemars(deny_unknown_fields)]struct ReadResponse {    resource: String,    contents: String,}#[derive(Clone)]pub(super) struct ReadTool {    pub(super) context: SkillToolContext,}impl ToolExecutor<ToolCall> for ReadTool {    fn tool_name(&self) -> ToolName {        skill_tool_name(TOOL_NAME)    }    fn spec(&self) -> ToolSpec {        skill_function_tool::<ReadArgs, ReadResponse>(            TOOL_NAME,            "Read one complete resource from an enabled skill. Pass the exact authority and package returned by skills.list; resource identifiers remain opaque and are routed to that authority.",        )    }    fn handle(&self, call: ToolCall) -> ToolExecutorFuture<'_> {        Box::pin(async move {            let args: ReadArgs = parse_args(&call)?;            let authority = args.authority.into_authority();            validate_handle("package", &args.package, MAX_HANDLE_BYTES)?;            validate_handle("resource", &args.resource, MAX_HANDLE_BYTES)?;            let catalog = self.context.catalog(&call.turn_id, args.authority).await;            let package_is_available = catalog.entries.iter().any(|entry| {                entry.enabled && entry.authority == authority && entry.id.0 == args.package            });            if !package_is_available {                return Err(FunctionCallError::RespondToModel(                    "skill package is not available from the requested authority".to_string(),                ));            }            let requested_resource = SkillResourceId::new(args.resource);            let result = self                .context                .thread_state                .read_skill(                    &self.context.providers,                    SkillReadRequest {                        authority,                        package: SkillPackageId(args.package),                        resource: requested_resource.clone(),                        host: None,                        mcp_resources: self.context.mcp_resources.clone(),                    },                )                .await                .map_err(|err| {                    tracing::warn!(                        error = %err,                        turn_id = %call.turn_id,                        call_id = %call.call_id,                        resource = requested_resource.as_str(),                        "skills.read provider request failed"                    );                    FunctionCallError::RespondToModel("failed to read skill resource".to_string())                })?;            if result.resource != requested_resource {                return Err(FunctionCallError::Fatal(                    "skill provider returned a different resource".to_string(),                ));            }            external_json_output(&ReadResponse {                resource: result.resource.as_str().to_string(),                contents: result.contents,            })        })    }}