| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| use super::*; | |
| use maka_agent_engine::{RunError, RunInput, RunWork}; | |
| use maka_agent_facts::input::InvocationInput; | |
| impl Owner { | |
| pub(super) fn effective_compaction_prompt( | |
| &self, | |
| snapshot: &RuntimePolicySnapshot, | |
| language: Language, | |
| ) -> String { | |
| snapshot | |
| .policy | |
| .compaction_prompt | |
| .clone() | |
| .unwrap_or_else(|| { | |
| maka_agent_facts::context::default_compaction_prompt(language).into() | |
| }) | |
| } | |
| pub async fn compaction_prompt(&self, input: SessionRead) -> Result<CompactionPrompt, String> { | |
| self.session(&input.session_id).await?; | |
| let snapshot = self | |
| .configuration | |
| .runtime_policy() | |
| .await | |
| .map_err(|_| "agent_configuration_unavailable")?; | |
| let language = self.resolve_language(&snapshot); | |
| Ok(CompactionPrompt { | |
| compaction_prompt: self.effective_compaction_prompt(&snapshot, language), | |
| default_compaction_prompt: maka_agent_facts::context::default_compaction_prompt( | |
| language, | |
| ) | |
| .into(), | |
| }) | |
| } | |
| pub async fn compact( | |
| self: &Arc<Self>, | |
| input: Compact, | |
| operation: &maka_plugin_core::operation::RecoveryRef, | |
| ) -> Result<CompactResult, String> { | |
| let mut runs = self.runs.lock().await; | |
| let session = self.session(&input.session_id).await?; | |
| let identity = self.operation_id(operation); | |
| let invocation = Invocation { | |
| session_id: input.session_id.clone(), | |
| turn_id: format!("t_{identity}"), | |
| run_id: format!("r_{identity}"), | |
| invocation_id: format!("i_{identity}"), | |
| }; | |
| // The canonical opening is the receipt, including after restart. | |
| if let Some(boundary) = self | |
| .store | |
| .invocation_boundary(&input.session_id, &invocation.invocation_id) | |
| .await | |
| .map_err(|_| "agent_execution_unavailable")? | |
| { | |
| if boundary.0.invocation != invocation | |
| || !matches!(&boundary.0.input, | |
| InvocationInput::ContextCompact { request_fingerprint } if request_fingerprint == &operation.fingerprint) | |
| { | |
| return Err("agent_compact_conflict".into()); | |
| } | |
| return Ok(CompactResult::Started { | |
| execution: execution::project(boundary), | |
| }); | |
| } | |
| let rejected = |reason| Ok(CompactResult::Rejected { reason }); | |
| if runs.closing || self.stop.is_cancelled() || runs.quarantined.contains(&input.session_id) | |
| { | |
| return rejected(CompactRejection::Closed); | |
| } | |
| if session.archived { | |
| return rejected(CompactRejection::Archived); | |
| } | |
| if runs.controls.contains_key(&input.session_id) | |
| || !self | |
| .store | |
| .message_queue(&input.session_id) | |
| .await | |
| .map_err(|_| "agent_queue_unavailable")? | |
| .entries | |
| .is_empty() | |
| { | |
| return rejected(CompactRejection::Busy); | |
| } | |
| if matches!( | |
| session.configuration.backend, | |
| Some(BackendPreference::Executor { .. }) | |
| ) { | |
| return rejected(CompactRejection::BackendUnavailable); | |
| } | |
| let workspace = &session.configuration.workspace; | |
| if workspace::validate(&self.workspace_roots, workspace, &self.managed_worktrees) | |
| .await | |
| .is_err() | |
| { | |
| return rejected(CompactRejection::WorkspaceChanged); | |
| } | |
| let (source, prepared) = crate::model::Source::select( | |
| self.configuration.clone(), | |
| self.catalog.clone(), | |
| self.credentials.clone(), | |
| input.model, | |
| input.thinking_level, | |
| input.session_id.clone(), | |
| self.revision.clone(), | |
| ) | |
| .await | |
| .map_err(public_error)?; | |
| let snapshot = self | |
| .configuration | |
| .runtime_policy() | |
| .await | |
| .map_err(|_| "agent_configuration_unavailable")?; | |
| let language = self.resolve_language(&snapshot); | |
| let prompt = match input.prompt { | |
| Some(prompt) => prompt, | |
| None => self.effective_compaction_prompt(&snapshot, language), | |
| }; | |
| let permissions = &session.configuration.permissions; | |
| let configuration = InvocationConfiguration { | |
| language, | |
| compaction_prompt: Some(prompt.clone()), | |
| editing_tools: source.accepted.editing_tools, | |
| tool_discovery: false, | |
| cwd: workspace.path.clone(), | |
| workspace_identity: Some(workspace.identity.clone()), | |
| workspace_origin: WorkspaceOrigin::Selected, | |
| sandbox_mode: match permissions.access { | |
| WorkspaceAccess::ReadOnly => SandboxMode::ReadOnly, | |
| WorkspaceAccess::WorkspaceWrite => SandboxMode::WorkspaceWrite, | |
| WorkspaceAccess::DangerFullAccess => SandboxMode::DangerFullAccess, | |
| }, | |
| approval_policy: if permissions.request_permissions { | |
| ApprovalPolicy::OnRequest | |
| } else { | |
| ApprovalPolicy::Never | |
| }, | |
| boundary_revision: session.configuration.boundary_revision, | |
| collaboration_mode: session.configuration.collaboration_mode, | |
| orchestration_mode: BehaviorId::default(), | |
| tool_mode: source.accepted.tool_mode, | |
| code_language: source.accepted.code_language, | |
| model: Some(source.accepted.binding.clone()), | |
| thinking_level: source.accepted.thinking_level, | |
| system_prompt: None, | |
| tool_composition: None, | |
| }; | |
| let basis = MessageBasis { | |
| language, | |
| compaction_prompt: Some(prompt), | |
| graph: None, | |
| collaboration_mode: session.configuration.collaboration_mode, | |
| background_kind: BackgroundKind::Schedule, | |
| tool_discovery: false, | |
| allowed_tools: session.configuration.tools.clone(), | |
| background: None, | |
| internal_tools: vec![], | |
| internal_bindings: BTreeMap::new(), | |
| behavior: None, | |
| model: Some(source.accepted.clone()), | |
| executor: None, | |
| workspace: workspace.clone(), | |
| fingerprint: operation.fingerprint.clone(), | |
| tools: vec![], | |
| }; | |
| let run = RunInput { | |
| invocation: invocation.clone(), | |
| model_source: Some(Arc::new(source)), | |
| model_revision: Some(prepared.revision), | |
| provider_id: prepared.provider_id, | |
| context: prepared.context, | |
| provider: prepared.provider, | |
| provider_options: prepared.options, | |
| main_output_limit: prepared.main_output_limit, | |
| supports_vision: prepared.supports_vision, | |
| request_fingerprint: Some(operation.fingerprint.clone()), | |
| configuration, | |
| work: RunWork::ContextCompact, | |
| }; | |
| let running = match self.engine.start(run, self.stop.child_token()).await { | |
| Ok(running) => running, | |
| Err(RunError::Busy) => return rejected(CompactRejection::Busy), | |
| Err(error) => { | |
| if error.is_cleanup_unconfirmed() { | |
| runs.quarantined.insert(input.session_id.clone()); | |
| runs.closing = true; | |
| self.stop.cancel(); | |
| } | |
| return Err(public_error(error)); | |
| } | |
| }; | |
| runs.controls.insert( | |
| input.session_id.clone(), | |
| Control { | |
| compacting: true, | |
| invocation: invocation.clone(), | |
| basis, | |
| cancellation: running.cancellation(), | |
| failure: None, | |
| }, | |
| ); | |
| self.workers.spawn(self.clone().drive(running)); | |
| let boundary = self | |
| .store | |
| .invocation_boundary(&input.session_id, &invocation.invocation_id) | |
| .await | |
| .map_err(|_| "agent_execution_unavailable")? | |
| .ok_or("agent_execution_unavailable")?; | |
| Ok(CompactResult::Started { | |
| execution: execution::project(boundary), | |
| }) | |
| } | |
| } |