blob: e964d1d2f67dd25e0ba0784e110c19c7a28a2134 [file]
/*
* 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 crate::{artifact::content_digest, tools::ToolDefinition};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeSet;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceKind {
ModelAdapter,
ModelProvider,
Input,
Tool,
PromptSection,
PromptVariable,
PromptContext,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SourceRevision {
pub kind: SourceKind,
pub name: String,
pub package_id: String,
pub entry_id: String,
pub activation: String,
pub revision: String,
}
/// The immutable capability surface of one logical model step. Conversation
/// history retains its own canonical prefix evidence in ModelRequested.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct RequestComposition {
pub system_prompt: Option<String>,
pub dynamic_context: Vec<String>,
pub tool_catalog_digest: String,
pub tools: Vec<ToolDefinition>,
pub provider_options: Option<Value>,
pub max_output_tokens: Option<u64>,
pub sources: Vec<SourceRevision>,
}
impl RequestComposition {
pub fn freeze(self) -> Result<FrozenComposition, &'static str> {
if !crate::archive::valid_projection_digest(&self.tool_catalog_digest) {
return Err("invalid tool catalog digest");
}
let mut names = BTreeSet::new();
for tool in &self.tools {
if tool.name.is_empty() || !names.insert(&tool.name) {
return Err("invalid request composition tools");
}
}
for source in &self.sources {
for value in [
&source.name,
&source.package_id,
&source.entry_id,
&source.activation,
&source.revision,
] {
if value.is_empty() || value.chars().any(char::is_control) {
return Err("invalid request composition source");
}
}
}
let bytes =
serde_json::to_vec(&self).map_err(|_| "invalid request composition encoding")?;
Ok(FrozenComposition {
digest: content_digest(&bytes),
bytes: bytes.into_boxed_slice(),
})
}
}
#[derive(Clone, Debug)]
pub struct FrozenComposition {
digest: String,
bytes: Box<[u8]>,
}
impl FrozenComposition {
pub fn digest(&self) -> &str {
&self.digest
}
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
}