roder-core 0.3.5

Agentic software development tools and SDKs for Roder.
Documentation
use super::*;

#[cfg(test)]
mod permission_tests {
    use super::*;

    #[tokio::test]
    async fn cached_goal_compaction_uses_current_permissions() {
        let runtime = Runtime::fake().unwrap();
        let id = "goal-compaction-permissions".to_string();
        let template = runtime
            .compaction_request_template(
                &id,
                &"turn".into(),
                roder_api::catalog::PROVIDER_MOCK,
                "mock-model",
            )
            .await
            .unwrap();
        runtime
            .compaction_templates
            .write()
            .await
            .insert(id.clone(), template);
        runtime
            .thread_goal_set(
                &id,
                roder_api::goals::ThreadGoalPatch {
                    objective: Some("Finish the authorized task".into()),
                    ..Default::default()
                },
            )
            .await
            .unwrap();
        for (mode, expected) in [
            (PolicyMode::Bypass, "Full Access"),
            (PolicyMode::Default, "Default mode"),
        ] {
            runtime.set_policy_mode(mode, None).await.unwrap();
            let template = runtime
                .compaction_request_template(
                    &id,
                    &"turn".into(),
                    roder_api::catalog::PROVIDER_MOCK,
                    "mock-model",
                )
                .await
                .unwrap();
            assert!(
                template
                    .instructions
                    .developer
                    .as_ref()
                    .unwrap()
                    .contains(&format!("Goal permissions: {expected}"))
            );
            if mode == PolicyMode::Default {
                assert!(
                    !template
                        .instructions
                        .developer
                        .as_ref()
                        .unwrap()
                        .contains("Goal permissions: Full Access")
                );
            }
        }
    }
}

impl Runtime {
    pub(crate) async fn compaction_request_template(
        &self,
        thread_id: &ThreadId,
        turn_id: &TurnId,
        provider: &str,
        model: &str,
    ) -> anyhow::Result<AgentInferenceRequest> {
        let mode = self.effective_policy_mode_for_thread(thread_id).await;
        if let Some(mut template) = self
            .compaction_templates
            .read()
            .await
            .get(thread_id)
            .cloned()
        {
            template.instructions = self
                .goals
                .apply_goal_instructions(thread_id, template.instructions, mode)
                .await?;
            return Ok(template);
        }
        let cfg = self.status().await;
        let overrides = self.thread_turn_overrides(thread_id).await?;
        let profile = model_profile_for_provider_model(&cfg, provider, model);
        let tools = self.filtered_tool_specs(
            &cfg,
            model,
            provider,
            profile.as_ref(),
            &overrides.tool_allowlist,
            &overrides.external_tools,
        );
        let context = self.active_turn_contexts.read().await.get(turn_id).cloned();
        let mut instructions = context
            .as_ref()
            .map(|context| context.instructions.clone())
            .unwrap_or_else(crate::default_instructions);
        if let Some(developer) = overrides.developer_instructions {
            instructions
                .developer
                .get_or_insert_default()
                .push_str(&format!("\n{developer}"));
        }
        instructions.developer_context = context.and_then(|context| context.developer_context);
        instructions = self
            .goals
            .apply_goal_instructions(thread_id, instructions, mode)
            .await?;
        Ok(AgentInferenceRequest {
            model: ModelSelection {
                provider: provider.into(),
                model: model.into(),
            },
            instructions,
            transcript: Vec::new(),
            tools,
            tool_choice: ToolChoice::Auto,
            reasoning: ReasoningConfig::default(),
            output: OutputConfig::default(),
            runtime: RuntimeHints::default(),
            metadata: serde_json::json!({}),
        })
    }

    pub(crate) async fn compaction_steering(
        &self,
        turn_id: &TurnId,
    ) -> Option<tokio::sync::watch::Receiver<u64>> {
        let active = self.active_turns.read().await.get(turn_id).cloned()?;
        let mut receiver = active.steer_changed.subscribe();
        if !active.steers.lock().await.is_empty() {
            receiver.mark_changed();
        }
        Some(receiver)
    }
}