Files
Genarrative/server-rs/crates/agent-runtime-core/src/provider.rs
T
kdletters dedff81475 修复AGC无人值守生成阻断的交付收口与验收
- 可信 code-prototype 父 Run 认领并观察直属美术 delivery,普通失败立即收束,合法安全默认 marker 由 Runtime 确定性执行唯一同合同返工

- 补齐 suppressed 无 child 与父身份链丢失时的 completion 失败关闭边界

- 修复 Windows project.lock delete-pending 竞争并收紧 HTML 内联 JS 语法 fail-open

- 同步技术方案、决策记录与实施计划,并完成确定性及真实 Provider 分层验证
2026-08-15 15:12:05 +08:00

1093 lines
32 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::contract::{
validate_description, validate_function_name, validate_identifier, validate_metadata,
};
macro_rules! provider_id {
($name:ident, $label:literal) => {
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct $name(String);
impl $name {
pub fn try_new(value: impl Into<String>) -> Result<Self, ProviderError> {
let value = value.into();
validate_identifier(&value, $label).map_err(ProviderError::from_contract)?;
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl TryFrom<String> for $name {
type Error = ProviderError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::try_new(value).map_err(serde::de::Error::custom)
}
}
impl fmt::Display for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
};
}
provider_id!(ProviderInstanceId, "provider instance id");
provider_id!(ProviderProtocolId, "provider protocol id");
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProviderCapability {
Streaming,
FunctionTools,
RequiredToolChoice,
ImageInput,
WebSearch,
ReasoningEffort,
TextVerbosity,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ProviderDescriptor {
instance_id: ProviderInstanceId,
protocol_id: ProviderProtocolId,
display_name: String,
capabilities: BTreeSet<ProviderCapability>,
metadata: Value,
}
impl ProviderDescriptor {
pub fn try_new(
instance_id: ProviderInstanceId,
protocol_id: ProviderProtocolId,
capabilities: impl IntoIterator<Item = ProviderCapability>,
) -> Result<Self, ProviderError> {
let descriptor = Self {
display_name: instance_id.as_str().to_string(),
instance_id,
protocol_id,
capabilities: capabilities.into_iter().collect(),
metadata: Value::Object(Map::new()),
};
descriptor.validate()?;
Ok(descriptor)
}
pub fn with_metadata(mut self, metadata: Value) -> Result<Self, ProviderError> {
validate_metadata(&metadata, "provider metadata").map_err(ProviderError::from_contract)?;
self.metadata = metadata;
Ok(self)
}
pub fn with_display_name(
mut self,
display_name: impl Into<String>,
) -> Result<Self, ProviderError> {
self.display_name = display_name.into();
self.validate()?;
Ok(self)
}
pub fn instance_id(&self) -> &ProviderInstanceId {
&self.instance_id
}
pub fn protocol_id(&self) -> &ProviderProtocolId {
&self.protocol_id
}
pub fn display_name(&self) -> &str {
&self.display_name
}
pub fn capabilities(&self) -> &BTreeSet<ProviderCapability> {
&self.capabilities
}
pub fn supports(&self, capability: ProviderCapability) -> bool {
self.capabilities.contains(&capability)
}
pub fn metadata(&self) -> &Value {
&self.metadata
}
fn validate(&self) -> Result<(), ProviderError> {
validate_description(&self.display_name, "provider display name")
.map_err(ProviderError::from_contract)?;
validate_metadata(&self.metadata, "provider metadata")
.map_err(ProviderError::from_contract)?;
if self.supports(ProviderCapability::RequiredToolChoice)
&& !self.supports(ProviderCapability::FunctionTools)
{
return Err(ProviderError::invalid(
"required-tool-choice capability 依赖 function-tools capability",
));
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProviderRole {
System,
User,
Assistant,
Tool,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(
deny_unknown_fields,
tag = "type",
rename_all = "kebab-case",
rename_all_fields = "camelCase"
)]
pub enum ProviderContentPart {
Text {
text: String,
},
Image {
source: Value,
},
ToolResult {
tool_call_id: String,
output: Value,
is_error: bool,
},
}
impl ProviderContentPart {
pub fn text(text: impl Into<String>) -> Result<Self, ProviderError> {
let text = text.into();
validate_non_empty_text(&text, "message text")?;
Ok(Self::Text { text })
}
pub fn image(source: Value) -> Result<Self, ProviderError> {
if !source.is_object() {
return Err(ProviderError::invalid("image source 必须是 JSON object"));
}
Ok(Self::Image { source })
}
pub fn tool_result(
tool_call_id: impl Into<String>,
output: Value,
is_error: bool,
) -> Result<Self, ProviderError> {
let tool_call_id = tool_call_id.into();
validate_identifier(&tool_call_id, "tool call id").map_err(ProviderError::from_contract)?;
Ok(Self::ToolResult {
tool_call_id,
output,
is_error,
})
}
fn validate(&self) -> Result<(), ProviderError> {
match self {
Self::Text { text } => validate_non_empty_text(text, "message text"),
Self::Image { source } if !source.is_object() => {
Err(ProviderError::invalid("image source 必须是 JSON object"))
}
Self::ToolResult { tool_call_id, .. } => {
validate_identifier(tool_call_id, "tool call id")
.map_err(ProviderError::from_contract)
}
Self::Image { .. } => Ok(()),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ProviderMessage {
role: ProviderRole,
content: Vec<ProviderContentPart>,
}
impl ProviderMessage {
pub fn try_new(
role: ProviderRole,
content: impl IntoIterator<Item = ProviderContentPart>,
) -> Result<Self, ProviderError> {
let message = Self {
role,
content: content.into_iter().collect(),
};
message.validate()?;
Ok(message)
}
pub fn role(&self) -> ProviderRole {
self.role
}
pub fn content(&self) -> &[ProviderContentPart] {
&self.content
}
fn validate(&self) -> Result<(), ProviderError> {
if self.content.is_empty() {
return Err(ProviderError::invalid("provider message content 不能为空"));
}
for part in &self.content {
part.validate()?;
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ProviderToolDefinition {
name: String,
description: String,
input_schema: Value,
strict: bool,
}
impl ProviderToolDefinition {
pub fn try_new(
name: impl Into<String>,
description: impl Into<String>,
input_schema: Value,
) -> Result<Self, ProviderError> {
let tool = Self {
name: name.into(),
description: description.into(),
input_schema,
strict: false,
};
tool.validate()?;
Ok(tool)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn description(&self) -> &str {
&self.description
}
pub fn input_schema(&self) -> &Value {
&self.input_schema
}
pub fn with_strict(mut self, strict: bool) -> Self {
self.strict = strict;
self
}
pub fn strict(&self) -> bool {
self.strict
}
fn validate(&self) -> Result<(), ProviderError> {
validate_function_name(&self.name).map_err(ProviderError::from_contract)?;
validate_description(&self.description, "provider tool description")
.map_err(ProviderError::from_contract)?;
if self.input_schema.get("type").and_then(Value::as_str) != Some("object") {
return Err(ProviderError::invalid(
"provider tool inputSchema.type 必须为 object",
));
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "type", content = "name", rename_all = "kebab-case")]
pub enum ProviderToolChoice {
Auto,
None,
Required,
Specific(String),
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProviderReasoningEffort {
Minimal,
Low,
Medium,
High,
XHigh,
Max,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProviderTextVerbosity {
Low,
Medium,
High,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ProviderRequest {
request_id: String,
model: Option<String>,
messages: Vec<ProviderMessage>,
max_output_tokens: Option<u32>,
request_timeout_ms: Option<u64>,
tools: Vec<ProviderToolDefinition>,
tool_choice: ProviderToolChoice,
web_search: bool,
reasoning_effort: Option<ProviderReasoningEffort>,
text_verbosity: Option<ProviderTextVerbosity>,
metadata: Value,
}
impl ProviderRequest {
pub fn try_new(
request_id: impl Into<String>,
messages: impl IntoIterator<Item = ProviderMessage>,
) -> Result<Self, ProviderError> {
let request = Self {
request_id: request_id.into(),
model: None,
messages: messages.into_iter().collect(),
max_output_tokens: None,
request_timeout_ms: None,
tools: Vec::new(),
tool_choice: ProviderToolChoice::Auto,
web_search: false,
reasoning_effort: None,
text_verbosity: None,
metadata: Value::Object(Map::new()),
};
request.validate()?;
Ok(request)
}
pub fn with_tools(
mut self,
tools: impl IntoIterator<Item = ProviderToolDefinition>,
tool_choice: ProviderToolChoice,
) -> Result<Self, ProviderError> {
self.tools = tools.into_iter().collect();
self.tool_choice = tool_choice;
self.validate()?;
Ok(self)
}
pub fn with_model(mut self, model: impl Into<String>) -> Result<Self, ProviderError> {
self.model = Some(model.into());
self.validate()?;
Ok(self)
}
pub fn with_max_output_tokens(mut self, max_output_tokens: u32) -> Result<Self, ProviderError> {
self.max_output_tokens = Some(max_output_tokens);
self.validate()?;
Ok(self)
}
pub fn with_request_timeout_ms(
mut self,
request_timeout_ms: u64,
) -> Result<Self, ProviderError> {
self.request_timeout_ms = Some(request_timeout_ms);
self.validate()?;
Ok(self)
}
pub fn with_web_search(mut self, enabled: bool) -> Self {
self.web_search = enabled;
self
}
pub fn with_reasoning_effort(mut self, effort: ProviderReasoningEffort) -> Self {
self.reasoning_effort = Some(effort);
self
}
pub fn with_text_verbosity(mut self, verbosity: ProviderTextVerbosity) -> Self {
self.text_verbosity = Some(verbosity);
self
}
pub fn with_metadata(mut self, metadata: Value) -> Result<Self, ProviderError> {
validate_metadata(&metadata, "provider request metadata")
.map_err(ProviderError::from_contract)?;
self.metadata = metadata;
Ok(self)
}
pub fn request_id(&self) -> &str {
&self.request_id
}
pub fn model(&self) -> Option<&str> {
self.model.as_deref()
}
pub fn messages(&self) -> &[ProviderMessage] {
&self.messages
}
pub fn max_output_tokens(&self) -> Option<u32> {
self.max_output_tokens
}
pub fn request_timeout_ms(&self) -> Option<u64> {
self.request_timeout_ms
}
pub fn tools(&self) -> &[ProviderToolDefinition] {
&self.tools
}
pub fn tool_choice(&self) -> &ProviderToolChoice {
&self.tool_choice
}
pub fn web_search(&self) -> bool {
self.web_search
}
pub fn reasoning_effort(&self) -> Option<ProviderReasoningEffort> {
self.reasoning_effort
}
pub fn text_verbosity(&self) -> Option<ProviderTextVerbosity> {
self.text_verbosity
}
pub fn metadata(&self) -> &Value {
&self.metadata
}
pub fn required_capabilities(&self) -> BTreeSet<ProviderCapability> {
let mut required = BTreeSet::new();
if !self.tools.is_empty() {
required.insert(ProviderCapability::FunctionTools);
}
if matches!(
self.tool_choice,
ProviderToolChoice::Required | ProviderToolChoice::Specific(_)
) {
required.insert(ProviderCapability::FunctionTools);
required.insert(ProviderCapability::RequiredToolChoice);
}
if self
.messages
.iter()
.flat_map(|message| message.content.iter())
.any(|part| matches!(part, ProviderContentPart::ToolResult { .. }))
{
required.insert(ProviderCapability::FunctionTools);
}
if self
.messages
.iter()
.flat_map(|message| message.content.iter())
.any(|part| matches!(part, ProviderContentPart::Image { .. }))
{
required.insert(ProviderCapability::ImageInput);
}
if self.web_search {
required.insert(ProviderCapability::WebSearch);
}
if self.reasoning_effort.is_some() {
required.insert(ProviderCapability::ReasoningEffort);
}
if self.text_verbosity.is_some() {
required.insert(ProviderCapability::TextVerbosity);
}
required
}
fn validate(&self) -> Result<(), ProviderError> {
validate_identifier(&self.request_id, "provider request id")
.map_err(ProviderError::from_contract)?;
if self
.model
.as_ref()
.is_some_and(|model| model.trim().is_empty())
{
return Err(ProviderError::invalid("provider request model 不能为空"));
}
if self.max_output_tokens == Some(0) {
return Err(ProviderError::invalid(
"provider request maxOutputTokens 必须大于 0",
));
}
if self.request_timeout_ms == Some(0) {
return Err(ProviderError::invalid(
"provider request requestTimeoutMs 必须大于 0",
));
}
if self.messages.is_empty() {
return Err(ProviderError::invalid("provider request messages 不能为空"));
}
for message in &self.messages {
message.validate()?;
}
let mut tool_names = BTreeSet::new();
for tool in &self.tools {
tool.validate()?;
if !tool_names.insert(tool.name.as_str()) {
return Err(ProviderError::invalid(format!(
"provider tool name 重复:{}",
tool.name
)));
}
}
match &self.tool_choice {
ProviderToolChoice::Auto | ProviderToolChoice::None => {}
ProviderToolChoice::Required if self.tools.is_empty() => {
return Err(ProviderError::invalid(
"required tool choice 需要至少一个 function tool",
));
}
ProviderToolChoice::Specific(name) => {
validate_function_name(name).map_err(ProviderError::from_contract)?;
if !tool_names.contains(name.as_str()) {
return Err(ProviderError::invalid(format!(
"specific tool choice 引用了未知工具:{name}"
)));
}
}
ProviderToolChoice::Required => {}
}
validate_metadata(&self.metadata, "provider request metadata")
.map_err(ProviderError::from_contract)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ProviderToolCall {
id: String,
name: String,
arguments: String,
}
impl ProviderToolCall {
pub fn try_new(
id: impl Into<String>,
name: impl Into<String>,
arguments: impl Into<String>,
) -> Result<Self, ProviderError> {
let call = Self {
id: id.into(),
name: name.into(),
arguments: arguments.into(),
};
validate_identifier(&call.id, "provider tool call id")
.map_err(ProviderError::from_contract)?;
validate_function_name(&call.name).map_err(ProviderError::from_contract)?;
Ok(call)
}
pub fn id(&self) -> &str {
&self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn arguments(&self) -> &str {
&self.arguments
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ProviderUsage {
input_tokens: u64,
output_tokens: u64,
total_tokens: u64,
}
impl ProviderUsage {
pub fn new(input_tokens: u64, output_tokens: u64, total_tokens: u64) -> Self {
Self {
input_tokens,
output_tokens,
total_tokens,
}
}
pub fn input_tokens(&self) -> u64 {
self.input_tokens
}
pub fn output_tokens(&self) -> u64 {
self.output_tokens
}
pub fn total_tokens(&self) -> u64 {
self.total_tokens
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ProviderResponse {
request_id: String,
model: String,
response_id: Option<String>,
content: Vec<ProviderContentPart>,
tool_calls: Vec<ProviderToolCall>,
finish_reason: Option<String>,
usage: Option<ProviderUsage>,
}
impl ProviderResponse {
pub fn try_new(
request_id: impl Into<String>,
model: impl Into<String>,
content: impl IntoIterator<Item = ProviderContentPart>,
tool_calls: impl IntoIterator<Item = ProviderToolCall>,
) -> Result<Self, ProviderError> {
let response = Self {
request_id: request_id.into(),
model: model.into(),
response_id: None,
content: content.into_iter().collect(),
tool_calls: tool_calls.into_iter().collect(),
finish_reason: None,
usage: None,
};
validate_identifier(&response.request_id, "provider response request id")
.map_err(ProviderError::from_contract)?;
validate_non_empty_text(&response.model, "provider response model")?;
Ok(response)
}
pub fn with_finish_reason(mut self, reason: impl Into<String>) -> Result<Self, ProviderError> {
let reason = reason.into();
validate_non_empty_text(&reason, "provider finish reason")?;
self.finish_reason = Some(reason);
Ok(self)
}
pub fn with_response_id(
mut self,
response_id: impl Into<String>,
) -> Result<Self, ProviderError> {
let response_id = response_id.into();
validate_non_empty_text(&response_id, "provider response id")?;
self.response_id = Some(response_id);
Ok(self)
}
pub fn with_usage(mut self, usage: ProviderUsage) -> Self {
self.usage = Some(usage);
self
}
pub fn request_id(&self) -> &str {
&self.request_id
}
pub fn model(&self) -> &str {
&self.model
}
pub fn response_id(&self) -> Option<&str> {
self.response_id.as_deref()
}
pub fn content(&self) -> &[ProviderContentPart] {
&self.content
}
pub fn tool_calls(&self) -> &[ProviderToolCall] {
&self.tool_calls
}
pub fn finish_reason(&self) -> Option<&str> {
self.finish_reason.as_deref()
}
pub fn usage(&self) -> Option<ProviderUsage> {
self.usage
}
fn validate_for_request(
&self,
request_id: &str,
declared_tools: &BTreeSet<String>,
) -> Result<(), ProviderError> {
if self.request_id != request_id {
return Err(ProviderError::invalid(
"provider response request id 与请求不匹配",
));
}
validate_non_empty_text(&self.model, "provider response model")?;
if self.content.is_empty() && self.tool_calls.is_empty() {
return Err(ProviderError::invalid(
"provider response 必须包含 content 或 toolCalls",
));
}
for part in &self.content {
part.validate()?;
}
let mut call_ids = BTreeSet::new();
for call in &self.tool_calls {
validate_identifier(&call.id, "provider tool call id")
.map_err(ProviderError::from_contract)?;
validate_function_name(&call.name).map_err(ProviderError::from_contract)?;
if !call_ids.insert(call.id.as_str()) {
return Err(ProviderError::invalid(format!(
"provider tool call id 重复:{}",
call.id
)));
}
if !declared_tools.contains(&call.name) {
return Err(ProviderError::invalid(format!(
"provider response 返回了未声明工具:{}",
call.name
)));
}
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(
deny_unknown_fields,
tag = "type",
rename_all = "kebab-case",
rename_all_fields = "camelCase"
)]
pub enum ProviderStreamEvent {
TextDelta {
accumulated_text: String,
delta_text: String,
finish_reason: Option<String>,
},
ToolCallDelta {
tool_call_id: String,
arguments_delta: String,
},
Usage {
usage: ProviderUsage,
},
}
pub trait ProviderStreamSink {
fn emit(&mut self, event: ProviderStreamEvent) -> Result<(), ProviderError>;
}
pub type ProviderFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub type ProviderStreamFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
pub trait ProviderAdapter: Send + Sync {
fn descriptor(&self) -> &ProviderDescriptor;
fn invoke(
&self,
request: ProviderRequest,
) -> ProviderFuture<'_, Result<ProviderResponse, ProviderError>>;
fn stream<'a>(
&'a self,
request: ProviderRequest,
sink: Box<dyn ProviderStreamSink + 'a>,
) -> ProviderStreamFuture<'a, Result<ProviderResponse, ProviderError>>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProviderTarget {
instance_id: ProviderInstanceId,
protocol_id: ProviderProtocolId,
}
impl ProviderTarget {
pub fn new(instance_id: ProviderInstanceId, protocol_id: ProviderProtocolId) -> Self {
Self {
instance_id,
protocol_id,
}
}
pub fn instance_id(&self) -> &ProviderInstanceId {
&self.instance_id
}
pub fn protocol_id(&self) -> &ProviderProtocolId {
&self.protocol_id
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProviderErrorKind {
InvalidContract,
InvalidConfig,
InvalidRequest,
DuplicateInstanceId,
UnknownInstanceId,
ProtocolMismatch,
CapabilityMismatch,
Timeout,
Connectivity,
Upstream,
StreamUnavailable,
EmptyResponse,
Transport,
Deserialize,
StreamSink,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProviderError {
kind: ProviderErrorKind,
detail: String,
attempts: Option<u32>,
status_code: Option<u16>,
}
impl ProviderError {
pub fn invalid_config(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::InvalidConfig, detail)
}
pub fn invalid_request(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::InvalidRequest, detail)
}
pub fn timeout(attempts: u32, detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::Timeout, detail).with_attempts(attempts)
}
pub fn connectivity(attempts: u32, detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::Connectivity, detail).with_attempts(attempts)
}
pub fn upstream(status_code: u16, detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::Upstream, detail).with_status_code(status_code)
}
pub fn stream_unavailable(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::StreamUnavailable, detail)
}
pub fn empty_response(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::EmptyResponse, detail)
}
pub fn transport(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::Transport, detail)
}
pub fn deserialize(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::Deserialize, detail)
}
pub fn stream_sink(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::StreamSink, detail)
}
fn new(kind: ProviderErrorKind, detail: impl Into<String>) -> Self {
Self {
kind,
detail: detail.into(),
attempts: None,
status_code: None,
}
}
fn with_attempts(mut self, attempts: u32) -> Self {
self.attempts = Some(attempts);
self
}
fn with_status_code(mut self, status_code: u16) -> Self {
self.status_code = Some(status_code);
self
}
fn invalid(detail: impl Into<String>) -> Self {
Self::new(ProviderErrorKind::InvalidContract, detail)
}
fn from_contract(error: crate::ContractError) -> Self {
Self::invalid(error.to_string())
}
pub fn kind(&self) -> ProviderErrorKind {
self.kind
}
pub fn detail(&self) -> &str {
&self.detail
}
pub fn attempts(&self) -> Option<u32> {
self.attempts
}
pub fn status_code(&self) -> Option<u16> {
self.status_code
}
}
impl fmt::Display for ProviderError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.detail)
}
}
impl std::error::Error for ProviderError {}
struct RegisteredProvider {
descriptor: ProviderDescriptor,
adapter: Arc<dyn ProviderAdapter>,
}
#[derive(Default)]
pub struct ProviderRegistry {
providers: BTreeMap<ProviderInstanceId, RegisteredProvider>,
}
impl ProviderRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn try_new(
adapters: impl IntoIterator<Item = Arc<dyn ProviderAdapter>>,
) -> Result<Self, ProviderError> {
let mut registry = Self::new();
for adapter in adapters {
registry.register(adapter)?;
}
Ok(registry)
}
pub fn register(&mut self, adapter: Arc<dyn ProviderAdapter>) -> Result<(), ProviderError> {
let descriptor = adapter.descriptor().clone();
descriptor.validate()?;
if self.providers.contains_key(descriptor.instance_id()) {
return Err(ProviderError::new(
ProviderErrorKind::DuplicateInstanceId,
format!("provider instance id 重复:{}", descriptor.instance_id()),
));
}
self.providers.insert(
descriptor.instance_id.clone(),
RegisteredProvider {
descriptor,
adapter,
},
);
Ok(())
}
pub fn len(&self) -> usize {
self.providers.len()
}
pub fn is_empty(&self) -> bool {
self.providers.is_empty()
}
pub fn descriptor(
&self,
instance_id: &ProviderInstanceId,
) -> Result<&ProviderDescriptor, ProviderError> {
self.providers
.get(instance_id)
.map(|entry| &entry.descriptor)
.ok_or_else(|| {
ProviderError::new(
ProviderErrorKind::UnknownInstanceId,
format!("未知 provider instance:{instance_id}"),
)
})
}
pub fn adapter(
&self,
instance_id: &ProviderInstanceId,
) -> Result<Arc<dyn ProviderAdapter>, ProviderError> {
self.providers
.get(instance_id)
.map(|entry| Arc::clone(&entry.adapter))
.ok_or_else(|| {
ProviderError::new(
ProviderErrorKind::UnknownInstanceId,
format!("未知 provider instance:{instance_id}"),
)
})
}
pub fn invoke(
&self,
target: &ProviderTarget,
request: ProviderRequest,
) -> ProviderFuture<'static, Result<ProviderResponse, ProviderError>> {
let adapter = match self.resolve(target, &request, false) {
Ok(adapter) => adapter,
Err(error) => return Box::pin(async move { Err(error) }),
};
let request_id = request.request_id.clone();
let declared_tools = request
.tools
.iter()
.map(|tool| tool.name.clone())
.collect::<BTreeSet<_>>();
Box::pin(async move {
let response = adapter.invoke(request).await?;
response.validate_for_request(&request_id, &declared_tools)?;
Ok(response)
})
}
pub fn stream<'a>(
&'a self,
target: &ProviderTarget,
request: ProviderRequest,
sink: Box<dyn ProviderStreamSink + 'a>,
) -> ProviderStreamFuture<'a, Result<ProviderResponse, ProviderError>> {
let adapter = match self.resolve(target, &request, true) {
Ok(adapter) => adapter,
Err(error) => return Box::pin(async move { Err(error) }),
};
let request_id = request.request_id.clone();
let declared_tools = request
.tools
.iter()
.map(|tool| tool.name.clone())
.collect::<BTreeSet<_>>();
Box::pin(async move {
let response = adapter.stream(request, sink).await?;
response.validate_for_request(&request_id, &declared_tools)?;
Ok(response)
})
}
fn resolve(
&self,
target: &ProviderTarget,
request: &ProviderRequest,
streaming: bool,
) -> Result<Arc<dyn ProviderAdapter>, ProviderError> {
request.validate()?;
let entry = self.providers.get(target.instance_id()).ok_or_else(|| {
ProviderError::new(
ProviderErrorKind::UnknownInstanceId,
format!("未知 provider instance:{}", target.instance_id()),
)
})?;
if entry.descriptor.protocol_id() != target.protocol_id() {
return Err(ProviderError::new(
ProviderErrorKind::ProtocolMismatch,
format!(
"provider instance {} 的 protocol 不匹配",
target.instance_id()
),
));
}
let mut required = request.required_capabilities();
if streaming {
required.insert(ProviderCapability::Streaming);
}
let missing = required
.difference(entry.descriptor.capabilities())
.copied()
.collect::<Vec<_>>();
if !missing.is_empty() {
return Err(ProviderError::new(
ProviderErrorKind::CapabilityMismatch,
format!(
"provider instance {} 缺少能力:{missing:?}",
target.instance_id()
),
));
}
Ok(Arc::clone(&entry.adapter))
}
}
fn validate_non_empty_text(value: &str, field: &str) -> Result<(), ProviderError> {
if value.trim().is_empty() {
Err(ProviderError::invalid(format!("{field} 不能为空")))
} else {
Ok(())
}
}