| /* | |
| * 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. | |
| */ | |
| pub use maka_sandbox::{Approval as ApprovalPolicy, Mode as SandboxMode}; | |
| use serde::{Deserialize, Serialize}; | |
| #[derive( | |
| Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema, | |
| )] | |
| #[serde(rename_all = "snake_case")] | |
| pub enum CollaborationMode { | |
| #[default] | |
| Agent, | |
| Plan, | |
| } | |
| /// A native tool surface selected by the Host, independent of plugin tool names. | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| pub enum ToolProfile { | |
| #[serde(rename = "headless-coding-v1")] | |
| HeadlessCodingV1, | |
| } | |
| mod behavior; | |
| pub use behavior::BehaviorId; | |
| mod tool_policy; | |
| pub use tool_policy::{CodeLanguage, EditingTools}; | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] | |
| #[serde(rename_all = "snake_case")] | |
| pub enum ThinkingLevel { | |
| Off, | |
| Minimal, | |
| Low, | |
| Medium, | |
| High, | |
| Xhigh, | |
| Max, | |
| Ultra, | |
| } | |
| impl ThinkingLevel { | |
| /// Display order; support still comes from the selected model's declaration. | |
| pub const ALL: [Self; 8] = [ | |
| Self::Off, | |
| Self::Minimal, | |
| Self::Low, | |
| Self::Medium, | |
| Self::High, | |
| Self::Xhigh, | |
| Self::Max, | |
| Self::Ultra, | |
| ]; | |
| } | |
| #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| #[serde(rename_all = "snake_case")] | |
| pub enum ToolMode { | |
| #[default] | |
| Direct, | |
| CodeMode, | |
| } | |
| /// Public model identity. Credentials and provider handles never belong here. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] | |
| pub struct ModelBinding { | |
| pub connection_id: String, | |
| pub connection_slug: String, | |
| pub model: String, | |
| } | |
| /// Actual Host-composed instructions admitted with this invocation, not a | |
| /// pointer to mutable root settings or a replacement for conversation history. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct SystemPrompt { | |
| pub text: String, | |
| pub policy_revision: u64, | |
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | |
| pub sources: Vec<crate::composition::SourceRevision>, | |
| } | |
| impl SystemPrompt { | |
| pub fn validate(&self) -> Result<(), &'static str> { | |
| if self.text.trim().is_empty() | |
| || self.policy_revision > crate::configuration::validation::MAX_SAFE_INTEGER | |
| { | |
| return Err("invalid system prompt"); | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// Immutable admission context, committed in the opening before dispatch. | |
| /// Later Session mutations must not reinterpret relative paths or grants. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct InvocationConfiguration { | |
| /// Frozen effective instructions for automatic and manual context compaction. | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| pub compaction_prompt: Option<String>, | |
| /// Frozen file-editing policy. Older invocations retain their original unfiltered tools. | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| pub editing_tools: Option<EditingTools>, | |
| /// Frozen discovery policy for this invocation, including continuations. | |
| #[serde(default, skip_serializing_if = "std::ops::Not::not")] | |
| pub tool_discovery: bool, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| pub tool_composition: Option<ToolComposition>, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| pub system_prompt: Option<SystemPrompt>, | |
| pub cwd: String, | |
| /// Captured from the workspace marker, never reconstructed from a later path. | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| pub workspace_identity: Option<WorkspaceIdentity>, | |
| pub workspace_origin: WorkspaceOrigin, | |
| pub sandbox_mode: SandboxMode, | |
| pub approval_policy: ApprovalPolicy, | |
| /// Identifies the admitted boundary even when a later configuration returns | |
| /// to the same modes and paths. Equality of values does not restore a grant. | |
| pub boundary_revision: u64, | |
| pub collaboration_mode: CollaborationMode, | |
| pub orchestration_mode: BehaviorId, | |
| pub tool_mode: ToolMode, | |
| /// Frozen Code Mode interpreter. Historical invocations used JavaScript. | |
| #[serde(default, skip_serializing_if = "CodeLanguage::is_javascript")] | |
| pub code_language: CodeLanguage, | |
| /// None means there was no Session-owned model binding (e.g. local Code). | |
| pub model: Option<ModelBinding>, | |
| pub thinking_level: Option<ThinkingLevel>, | |
| #[serde(default, skip_serializing_if = "crate::language::Language::is_english")] | |
| pub language: crate::language::Language, | |
| } | |
| /// Admission evidence for Host-owned handlers whose behavior is not in schema. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| #[serde(deny_unknown_fields)] | |
| pub struct ToolComposition { | |
| pub clients: crate::capability::ClientComposition, | |
| #[serde(default)] | |
| pub native_tools: NativeToolSet, | |
| #[serde(default)] | |
| pub editing_tools: EditingTools, | |
| /// Frozen Client capabilities usable by Host services, not advertised to the model. | |
| #[serde(default, skip_serializing_if = "std::collections::BTreeSet::is_empty")] | |
| pub private_clients: std::collections::BTreeSet<String>, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| pub bound_tools: Option<std::collections::BTreeSet<String>>, | |
| } | |
| /// Host-owned handlers retain their access boundary across recovery. A plugin | |
| /// may narrow the baseline, but cannot replace a core handler with its own. | |
| #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| #[serde(rename_all = "snake_case")] | |
| pub enum NativeToolSet { | |
| #[default] | |
| Workspace, | |
| Attachments, | |
| } | |
| /// User-selected workspace locator, before Host path resolution. | |
| #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
| #[serde( | |
| tag = "kind", | |
| rename_all = "snake_case", | |
| rename_all_fields = "camelCase", | |
| deny_unknown_fields | |
| )] | |
| pub enum WorkspaceTarget { | |
| Project { project_id: String }, | |
| HostPath { path: String }, | |
| } | |
| /// Host-resolved location; a locator alone never authorizes filesystem access. | |
| #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | |
| #[serde(rename_all = "camelCase", deny_unknown_fields)] | |
| pub struct WorkspaceProjection { | |
| pub target: WorkspaceTarget, | |
| pub host_cwd: String, | |
| } | |
| /// Host-issued provenance recorded with execution authority, never inferred from | |
| /// the display projection's path or supplied by a workspace locator. | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| #[serde(rename_all = "snake_case")] | |
| pub enum WorkspaceOrigin { | |
| Selected, | |
| Allocated, | |
| } | |
| /// Intrinsic workspace identity is distinct from its current filesystem location. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| #[serde(try_from = "String", into = "String")] | |
| pub struct WorkspaceIdentity(String); | |
| /// Filesystem-object observation for directory consent, not an execution workspace marker. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| #[serde(try_from = "String", into = "String")] | |
| pub struct DirectoryIdentity(String); | |
| impl TryFrom<String> for DirectoryIdentity { | |
| type Error = &'static str; | |
| fn try_from(value: String) -> Result<Self, Self::Error> { | |
| if value.strip_prefix("sha256:").is_none_or(|hex| { | |
| hex.len() != 64 | |
| || !hex | |
| .bytes() | |
| .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) | |
| }) { | |
| return Err("invalid directory identity"); | |
| } | |
| Ok(Self(value)) | |
| } | |
| } | |
| impl From<DirectoryIdentity> for String { | |
| fn from(identity: DirectoryIdentity) -> Self { | |
| identity.0 | |
| } | |
| } | |
| impl WorkspaceIdentity { | |
| pub fn from_marker_id(id: &str) -> Result<Self, &'static str> { | |
| let uuid = uuid::Uuid::parse_str(id).map_err(|_| "invalid workspace UUID")?; | |
| if id.len() != 36 | |
| || !uuid.hyphenated().to_string().eq_ignore_ascii_case(id) | |
| || !(1..=8).contains(&uuid.get_version_num()) | |
| || uuid.get_variant() != uuid::Variant::RFC4122 | |
| { | |
| return Err("invalid workspace UUID"); | |
| } | |
| // Preserve an existing marker's exact spelling, including uppercase UUIDs. | |
| Ok(Self(format!("workspace:v1:{id}"))) | |
| } | |
| pub fn marker_id(&self) -> &str { | |
| &self.0["workspace:v1:".len()..] | |
| } | |
| } | |
| impl TryFrom<String> for WorkspaceIdentity { | |
| type Error = &'static str; | |
| fn try_from(value: String) -> Result<Self, Self::Error> { | |
| Self::from_marker_id( | |
| value | |
| .strip_prefix("workspace:v1:") | |
| .ok_or("invalid workspace identity")?, | |
| ) | |
| } | |
| } | |
| impl From<WorkspaceIdentity> for String { | |
| fn from(identity: WorkspaceIdentity) -> Self { | |
| identity.0 | |
| } | |
| } | |
| #[cfg(test)] | |
| mod language_tests { | |
| use super::*; | |
| use crate::language::Language; | |
| #[test] | |
| fn canonical_english_default_and_chinese_invocation_fields() { | |
| const ORIGINAL: &str = r#"{"cwd":"/tmp","workspace_origin":"selected","sandbox_mode":"workspace-write","approval_policy":{"kind":"on-request"},"boundary_revision":0,"collaboration_mode":"agent","orchestration_mode":"default","tool_mode":"direct","model":null,"thinking_level":null}"#; | |
| let mut configuration: InvocationConfiguration = serde_json::from_str(ORIGINAL).unwrap(); | |
| assert_eq!(configuration.language, Language::En); | |
| assert_eq!(serde_json::to_string(&configuration).unwrap(), ORIGINAL); | |
| for (language, tag) in [(Language::ZhCn, "zh-CN"), (Language::ZhTw, "zh-TW")] { | |
| configuration.language = language; | |
| let encoded = serde_json::to_value(&configuration).unwrap(); | |
| assert_eq!(encoded["language"], tag); | |
| assert_eq!( | |
| serde_json::from_value::<InvocationConfiguration>(encoded) | |
| .unwrap() | |
| .language, | |
| language | |
| ); | |
| } | |
| } | |
| } |