use crate::tools::ToolCallInfo;
use crate::{
error::TypeError,
prompt::{MessageNum, ModelSettings, ResponseContent},
tools::AgentToolDefinition,
Provider,
};
use potato_util::utils::TokenLogProbs;
use pyo3::prelude::*;
use pyo3::types::PyList;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::OnceLock;
pub static VAR_REGEX: OnceLock<Regex> = OnceLock::new();
pub fn get_var_regex() -> &'static Regex {
VAR_REGEX.get_or_init(|| Regex::new(r"\$?\{([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap())
}
pub static MEDIA_REGEX: OnceLock<Regex> = OnceLock::new();
pub fn get_media_regex() -> &'static Regex {
MEDIA_REGEX.get_or_init(|| Regex::new(r"\$\{media:([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap())
}
pub(crate) fn split_text_on_media(text: &str, regex: &Regex) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
let mut last = 0usize;
for m in regex.find_iter(text) {
if m.start() > last {
out.push(text[last..m.start()].to_string());
}
out.push(m.as_str().to_string());
last = m.end();
}
if last < text.len() {
out.push(text[last..].to_string());
}
out
}
pub(crate) fn extract_token_name(token: &str) -> String {
token
.strip_prefix("${media:")
.and_then(|s| s.strip_suffix('}'))
.unwrap_or("")
.to_string()
}
use crate::prompt::builder::ProviderRequest;
pub trait PromptMessageExt:
Send + Sync + Clone + Serialize + for<'de> Deserialize<'de> + PartialEq
{
fn bind(&self, name: &str, value: &str) -> Result<Self, TypeError>
where
Self: Sized;
fn bind_mut(&mut self, name: &str, value: &str) -> Result<(), TypeError>;
fn extract_variables(&self) -> Vec<String>;
fn from_text(content: String, role: &str) -> Result<Self, TypeError>;
fn extract_media_variables(&self) -> Vec<String> {
Vec::new()
}
fn split_media_placeholders(&mut self) -> Result<(), TypeError> {
Ok(())
}
fn bind_media_mut(
&mut self,
_token: &str,
_media: &crate::prompt::media::MediaRef,
_provider: &crate::Provider,
) -> Result<bool, TypeError> {
Ok(false)
}
}
pub trait RequestAdapter {
fn messages(&self) -> &[MessageNum];
fn messages_mut(&mut self) -> &mut Vec<MessageNum>;
fn system_instructions(&self) -> Vec<&MessageNum>;
fn response_json_schema(&self) -> Option<&Value>;
fn insert_message(&mut self, message: MessageNum, idx: Option<usize>) {
self.messages_mut().insert(idx.unwrap_or(0), message);
}
fn preprend_system_instructions(&mut self, messages: Vec<MessageNum>) -> Result<(), TypeError>;
fn get_py_system_instructions<'py>(
&self,
py: Python<'py>,
) -> Result<Bound<'py, PyList>, TypeError>;
fn model_settings<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyAny>, TypeError>;
fn to_request_body(&self) -> Result<Value, TypeError>;
fn match_provider(&self, provider: &Provider) -> bool;
fn build_provider_enum(
messages: Vec<MessageNum>,
system_instructions: Vec<MessageNum>,
model: String,
settings: ModelSettings,
response_json_schema: Option<Value>,
) -> Result<ProviderRequest, TypeError>;
fn set_response_json_schema(&mut self, response_json_schema: Option<Value>) -> ();
fn add_tools(&mut self, tools: Vec<AgentToolDefinition>) -> Result<(), TypeError>;
}
pub trait ResponseAdapter {
fn __str__(&self) -> String;
fn is_empty(&self) -> bool;
fn to_bound_py_object<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyAny>, TypeError>;
fn id(&self) -> &str;
fn to_message_num(&self) -> Result<Vec<MessageNum>, TypeError>;
fn usage<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyAny>, TypeError>;
fn get_content(&self) -> ResponseContent;
fn get_log_probs(&self) -> Vec<TokenLogProbs>;
fn structured_output<'py>(
&self,
py: Python<'py>,
output_type: Option<&Bound<'py, PyAny>>,
) -> Result<Bound<'py, PyAny>, TypeError>;
fn structured_output_value(&self) -> Option<Value>;
fn tool_call_output(&self) -> Option<Value>;
fn response_text(&self) -> String;
fn extract_tool_calls(&self) -> Option<Vec<crate::tools::ToolCall>> {
None
}
fn model_name(&self) -> Option<&str>;
fn finish_reason(&self) -> Option<&str>;
fn input_tokens(&self) -> Option<i64>;
fn output_tokens(&self) -> Option<i64>;
fn total_tokens(&self) -> Option<i64>;
fn get_tool_calls(&self) -> Vec<ToolCallInfo>;
}
pub trait MessageResponseExt {
fn to_message_num(&self) -> Result<MessageNum, TypeError>;
}
pub trait MessageFactory: Sized {
fn from_text(content: String, role: &str) -> Result<Self, TypeError>;
}
pub trait MessageConversion {
fn to_anthropic_message(
&self,
) -> Result<crate::anthropic::v1::request::MessageParam, TypeError>;
fn to_google_message(
&self,
) -> Result<crate::google::v1::generate::request::GeminiContent, TypeError>;
fn to_openai_message(&self)
-> Result<crate::openai::v1::chat::request::ChatMessage, TypeError>;
}