everruns-core 0.43.0

Transport-neutral agent execution contracts for Everruns
Documentation
    fn utility_checks_before_jev(stage: &str, check_type: &str) -> serde_json::Value {
        let utility_cap = if check_type == "moderation" {
            MAX_MODERATION_CALLS_PER_INVOCATION
        } else {
            MAX_JUDGE_CALLS_PER_INVOCATION
        };
        let mut checks = (0..=utility_cap)
            .map(|index| {
                if check_type == "moderation" {
                    json!({
                        "id": format!("utility_{index}"),
                        "stage": stage,
                        "type": "moderation"
                    })
                } else {
                    json!({
                        "id": format!("utility_{index}"),
                        "stage": stage,
                        "type": "llm_judge",
                        "prompt": "allow"
                    })
                }
            })
            .collect::<Vec<_>>();
        checks.push(if check_type == "moderation" {
            json!({
                "id": "jev_block",
                "stage": stage,
                "type": "moderation",
                "engine": "jev"
            })
        } else {
            json!({
                "id": "jev_block",
                "stage": stage,
                "type": "llm_judge",
                "engine": "jev",
                "prompt": "block"
            })
        });
        json!({"checks": checks})
    }

    #[tokio::test]
    async fn utility_moderation_cap_does_not_skip_later_jev_check() {
        let config = utility_checks_before_jev("output", "moderation");
        let utility: Arc<dyn UtilityLlmService> = scores(r#"{"scores":{"hate":0}}"#);
        let decision = run_moderation_seam_with(
            &config,
            Some(utility),
            Some(StubJudgment::severe(1.0)),
            "blocked content",
        )
        .await;

        assert!(matches!(decision, GuardrailDecision::Block(_)));
    }

    #[tokio::test]
    async fn utility_tool_use_cap_does_not_skip_later_jev_check() {
        let config = utility_checks_before_jev("tool_use", "llm_judge");
        let hooks = GuardrailsCapability.pre_tool_use_hooks_with_config(&config);
        let ctx = ToolContext::new(SessionId::new())
            .with_utility_llm_service(StubJudge::allow())
            .with_decisions(StubJudgment::noul(1.0));
        let decision = hooks[0]
            .before_exec(tool_call("any_tool", json!({})), &tool_def(), &ctx)
            .await;

        assert!(matches!(decision, PreToolUseDecision::Block { .. }));
    }

    #[tokio::test]
    async fn utility_tool_output_cap_does_not_skip_later_jev_check() {
        let config = utility_checks_before_jev("tool_output", "llm_judge");
        let hooks = GuardrailsCapability.post_tool_exec_hooks_with_config(&config);
        let ctx = ToolContext::new(SessionId::new())
            .with_utility_llm_service(StubJudge::allow())
            .with_decisions(StubJudgment::noul(1.0));
        let mut result = ToolResult {
            tool_call_id: "call_1".to_string(),
            result: Some(json!("blocked content")),
            images: None,
            error: None,
            connection_required: None,
            raw_output: Some("blocked raw output".to_string()),
        };
        hooks[0]
            .after_exec(
                &tool_call("any_tool", json!({})),
                &tool_def(),
                &mut result,
                &ctx,
            )
            .await;

        assert_eq!(result.result, Some(json!(DEFAULT_TOOL_OUTPUT_REPLACEMENT)));
        assert!(result.raw_output.is_none());
    }