use std::collections::BTreeMap;
use runx_contracts::schema::NonEmptyString;
use runx_contracts::{
ContextArtifactMeta, ContextArtifactProducer, ContextEntry, ContextEntryVersion, JsonObject,
JsonValue, sha256_prefixed,
};
use crate::RuntimeError;
const CONTEXT_ENTRY_TYPE: &str = "runx.skill.context";
const CONTEXT_PRODUCER_SKILL: &str = "runx-runtime";
const CONTEXT_PRODUCER_RUNNER: &str = "skill-context";
const PENDING_RUN_ID: &str = "rx_pending";
pub(super) struct SkillContextEntryInput<'a> {
pub(super) step_id: &'a str,
pub(super) reference: &'a str,
pub(super) env: &'a BTreeMap<String, String>,
pub(super) created_at: &'a str,
pub(super) digest: &'a str,
pub(super) size_bytes: u64,
pub(super) data: JsonObject,
}
pub(super) fn skill_context_entry(
input: SkillContextEntryInput<'_>,
) -> Result<ContextEntry, RuntimeError> {
let artifact_id = sha256_prefixed(
format!(
"{CONTEXT_ENTRY_TYPE}\0{}\0{}",
input.reference, input.digest
)
.as_bytes(),
);
Ok(ContextEntry {
entry_type: Some(non_empty(CONTEXT_ENTRY_TYPE)?),
version: ContextEntryVersion::V1,
data: input.data,
meta: ContextArtifactMeta {
artifact_id: non_empty(artifact_id)?,
run_id: non_empty(
input
.env
.get(crate::execution::runner::RUNX_RUN_ID_ENV)
.map(String::as_str)
.unwrap_or(PENDING_RUN_ID),
)?,
step_id: Some(non_empty(input.step_id)?),
producer: ContextArtifactProducer {
skill: non_empty(CONTEXT_PRODUCER_SKILL)?,
runner: non_empty(CONTEXT_PRODUCER_RUNNER)?,
},
created_at: non_empty(input.created_at)?,
hash: non_empty(input.digest)?,
size_bytes: input.size_bytes,
parent_artifact_id: None,
receipt_id: None,
redacted: false,
},
})
}
pub(super) fn insert_string(object: &mut JsonObject, key: &str, value: &str) {
object.insert(key.to_owned(), JsonValue::String(value.to_owned()));
}
fn non_empty(value: impl Into<String>) -> Result<NonEmptyString, RuntimeError> {
NonEmptyString::new(value.into()).ok_or_else(|| RuntimeError::ReceiptInvalid {
message: "skill context artifact included an empty required field".to_owned(),
})
}