Files
Genarrative/server-rs/crates/agent-runtime-core/src/contract.rs
T
AIGameCreator App d4075c3423
Project CI / Frontend tests (push) Failing after 3m43s
Project CI / Native shell tests (push) Failing after 3m36s
Project CI / Repository checks (push) Failing after 4m36s
Project CI / Backend tests (push) Successful in 8m36s
抽取通用多智能体运行时与可扩展Provider
新增纯 Rust agent-runtime-core,提供运行时契约、能力注册、Agent 目录、Profile、完成策略与零重放恢复
抽取中立 LLM Provider 协议与可扩展 Registry,并适配 OpenAI Responses、OpenAI Chat 和 Anthropic
将 AGC interaction、Provider 控制、Runner 生命周期、steering 与 tool-plan handoff 接入统一运行时边界
补充非游戏消费者、Provider 网络闭环、Runtime 恢复及 GUI Runner owner 测试
同步 Cargo/npm 门禁、Runtime 技术方案和项目共享记忆
2026-07-30 16:15:51 +08:00

122 lines
3.4 KiB
Rust

use std::fmt;
use serde_json::Value;
const IDENTIFIER_MAX_CHARS: usize = 128;
const DESCRIPTION_MAX_CHARS: usize = 4_000;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ContractErrorKind {
InvalidDefinition,
DuplicateId,
DuplicateReference,
UnknownCapability,
UnknownCompletionPolicy,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContractError {
kind: ContractErrorKind,
detail: String,
}
impl ContractError {
pub fn new(kind: ContractErrorKind, detail: impl Into<String>) -> Self {
Self {
kind,
detail: detail.into(),
}
}
pub fn kind(&self) -> ContractErrorKind {
self.kind
}
pub fn detail(&self) -> &str {
&self.detail
}
}
impl fmt::Display for ContractError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.detail)
}
}
impl std::error::Error for ContractError {}
pub(crate) fn validate_identifier(value: &str, field: &str) -> Result<(), ContractError> {
if value != value.trim() {
return Err(ContractError::new(
ContractErrorKind::InvalidDefinition,
format!("{field} 不得包含首尾空白"),
));
}
let mut chars = value.chars();
let first = chars.next().ok_or_else(|| {
ContractError::new(
ContractErrorKind::InvalidDefinition,
format!("{field} 不能为空"),
)
})?;
if value.chars().count() > IDENTIFIER_MAX_CHARS
|| !first.is_ascii_alphanumeric()
|| !chars.all(|character| {
character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-' | ':')
})
{
return Err(ContractError::new(
ContractErrorKind::InvalidDefinition,
format!("{field} 不是合法稳定标识:{value}"),
));
}
Ok(())
}
pub(crate) fn validate_function_name(value: &str) -> Result<(), ContractError> {
if value != value.trim() {
return Err(ContractError::new(
ContractErrorKind::InvalidDefinition,
"functionName 不得包含首尾空白",
));
}
let mut chars = value.chars();
let first = chars.next().ok_or_else(|| {
ContractError::new(
ContractErrorKind::InvalidDefinition,
"functionName 不能为空",
)
})?;
if value.chars().count() > IDENTIFIER_MAX_CHARS
|| !(first.is_ascii_alphabetic() || first == '_')
|| !chars.all(|character| character.is_ascii_alphanumeric() || character == '_')
{
return Err(ContractError::new(
ContractErrorKind::InvalidDefinition,
format!("functionName 不是合法函数标识:{value}"),
));
}
Ok(())
}
pub(crate) fn validate_description(value: &str, field: &str) -> Result<(), ContractError> {
let chars = value.trim().chars().count();
if chars == 0 || chars > DESCRIPTION_MAX_CHARS {
return Err(ContractError::new(
ContractErrorKind::InvalidDefinition,
format!("{field} 必须为 1..={DESCRIPTION_MAX_CHARS} 个字符"),
));
}
Ok(())
}
pub(crate) fn validate_metadata(metadata: &Value, field: &str) -> Result<(), ContractError> {
if !metadata.is_object() {
return Err(ContractError::new(
ContractErrorKind::InvalidDefinition,
format!("{field} 必须是 JSON object"),
));
}
Ok(())
}