///|
/// ValidateResult — 校验结果
pub struct ValidateResult {
valid : Bool
errors : Array[String]
warnings : Array[String]
}
///|
pub fn ValidateResult::ok() -> ValidateResult {
{ valid: true, errors: [], warnings: [], }
}
///|
pub fn ValidateResult::error(msg : String) -> ValidateResult {
{ valid: false, errors: [msg], warnings: [], }
}
///|
/// 校验 LucentRequest 的字段值合法性(与 ProviderCapability 无关的基本校验)
/// 例如:temperature 范围 [0, 2],max_output_tokens >= 1,model 非空
pub fn LucentRequest::validate(self : LucentRequest) -> ValidateResult {
let errors : Array[String] = []
let warnings : Array[String] = []
if self.model == "" {
errors.push("model is required")
}
match self.options.temperature {
Some(t) =>
if t < 0.0 || t > 2.0 {
errors.push("temperature must be in [0, 2], got " + t.to_string())
}
None => ()
}
match self.options.top_p {
Some(p) =>
if p < 0.0 || p > 1.0 {
errors.push("top_p must be in [0, 1], got " + p.to_string())
}
None => ()
}
match self.options.top_k {
Some(k) =>
if k < 1 {
errors.push("top_k must be >= 1, got " + k.to_string())
}
None => ()
}
match self.options.max_output_tokens {
Some(m) =>
if m < 1 {
errors.push("max_output_tokens must be >= 1, got " + m.to_string())
}
None => ()
}
{ valid: errors.length() == 0, errors, warnings, }
}
///|
/// 校验 LucentResponse 字段合法性
pub fn LucentResponse::validate(self : LucentResponse) -> ValidateResult {
let errors : Array[String] = []
if self.id == "" {
errors.push("response id is required")
}
if self.model == "" {
errors.push("response model is required")
}
if self.choices.length() == 0 {
errors.push("response must have at least one choice")
}
{ valid: errors.length() == 0, errors, warnings: [], }
}
///|
/// 构建含新 conversation 的请求(子包后处理用)
pub fn LucentRequest::with_conversation(
self : LucentRequest,
conversation : Array[LucentConversationItem],
) -> LucentRequest {
{ ..self, conversation, }
}
///|
/// 构建含 stream 标志的请求副本(SDK 使用)
pub fn LucentRequest::with_stream(
self : LucentRequest,
stream : Bool,
) -> LucentRequest {
{ ..self, options: { ..self.options, stream, }, }
}
///|
/// 便利构造器:用 messages 数组(旧风格)替代 conversation
pub fn LucentRequest::from_messages(
model : String,
instructions : Array[LucentContent]?,
messages : Array[LucentMessage],
tools : Array[LucentTool]?,
tool_choice : LucentToolChoice?,
options : LucentOptions,
capabilities : LucentCapabilities?,
reasoning : LucentReasoningConfig?,
metadata : Map[String, String]?,
extra : Map[String, Json]?,
) -> LucentRequest {
{
schema_version: "v1",
model,
instructions,
conversation: lucent_items_from_messages(messages),
tools,
tool_choice,
options,
capabilities,
reasoning,
metadata,
extra,
}
}
///|
pub fn LucentUsage::new(
prompt_tokens : Int,
completion_tokens : Int,
total_tokens : Int,
) -> LucentUsage {
{
prompt_tokens,
completion_tokens,
total_tokens,
reasoning_tokens: None,
cached_tokens: None,
cache_creation_tokens: None,
}
}
///|
/// 构造含细化用量的 LucentUsage(reasoning / cached / cache_creation)
pub fn LucentUsage::full(
prompt_tokens : Int,
completion_tokens : Int,
total_tokens : Int,
reasoning_tokens : Int?,
cached_tokens : Int?,
cache_creation_tokens : Int?,
) -> LucentUsage {
{
prompt_tokens,
completion_tokens,
total_tokens,
reasoning_tokens,
cached_tokens,
cache_creation_tokens,
}
}
///|
pub fn LucentUsage::with_reasoning(
prompt_tokens : Int,
completion_tokens : Int,
total_tokens : Int,
reasoning_tokens : Int,
) -> LucentUsage {
{
prompt_tokens,
completion_tokens,
total_tokens,
reasoning_tokens: Some(reasoning_tokens),
cached_tokens: None,
cache_creation_tokens: None,
}
}
///|
pub fn LucentSafetyRating::new(
category : String,
probability : String,
) -> LucentSafetyRating {
{ category, probability, }
}
///|
pub fn LucentChoice::new(
index : Int,
message : LucentMessage,
finish_reason : LucentFinishReason,
safety_ratings : Array[LucentSafetyRating]?,
) -> LucentChoice {
{ index, message, finish_reason, safety_ratings, }
}
///|
pub fn LucentResponse::new(
id : String,
model : String,
created_at : Int?,
choices : Array[LucentChoice],
usage : LucentUsage?,
provider_payload : Json?,
) -> LucentResponse {
{
schema_version: "v1",
id,
model,
created_at,
choices,
usage,
provider_payload,
}
}
///|
/// 构建含 provider_payload 的新响应(外部包访问用)
pub fn LucentResponse::with_provider_payload(
self : LucentResponse,
payload : Json?,
) -> LucentResponse {
{ ..self, provider_payload: payload, }
}
///|
/// 提取响应中的纯文本(L1 SDK 使用)
pub fn LucentResponse::get_text(self : LucentResponse) -> String {
if self.choices.length() > 0 {
lucent_content_to_text(self.choices[0].message.content)
} else {
""
}
}
///|
/// 便捷访问:首个 choice 的消息级 reasoning(SDK 取用入口)
pub fn LucentResponse::reasoning(self : LucentResponse) -> LucentThinking? {
if self.choices.length() > 0 {
self.choices[0].message.reasoning
} else {
None
}
}
///|
/// LucentFinishReason 字符串映射(适配器共用)
pub fn LucentFinishReason::from_string(s : String) -> LucentFinishReason {
match s {
"stop" | "end_turn" | "completed" | "STOP" => Stop
"length" | "max_tokens" | "MAX_TOKENS" | "incomplete" => Length
"tool_calls" | "tool_use" | "function_call" => ToolCalls
"content_filter" => ContentFilter
"SAFETY" | "safety" => Safety
"RECITATION" | "recitation" => Recitation
"MALFORMED_FUNCTION_CALL" | "malformed_function_call" => MalformedToolCall
"error" | "failed" | "ERROR" => Error
other => Native(other)
}
}
///|
pub fn LucentFinishReason::to_string(self : LucentFinishReason) -> String {
match self {
Stop => "stop"
Length => "length"
ToolCalls => "tool_calls"
ContentFilter => "content_filter"
Safety => "safety"
Recitation => "recitation"
MalformedToolCall => "malformed_function_call"
Error => "error"
Native(s) => s
}
}
///|
/// LucentReasoningEffort 字符串映射(适配器共用)
///
/// 出站侧 openai / messages / responses 三处使用完全相同的档位名
/// ("low"/"medium"/"high"/"xhigh"),集中于此避免重复 match。
/// gemini 的降级映射(XHigh → ("high", true))语义不同,保留在 adapter。
pub fn LucentReasoningEffort::to_string(self : LucentReasoningEffort) -> String {
match self {
Low => "low"
Medium => "medium"
High => "high"
XHigh => "xhigh"
}
}
///|
/// 从 conversation items 中提取所有 Message 项(用于适配器需要 messages 视图的场景)
pub fn lucent_messages_from_items(
items : Array[LucentConversationItem],
) -> Array[LucentMessage] {
let result : Array[LucentMessage] = []
for item in items {
match item {
Message(m) => result.push(m)
_ => ()
}
}
result
}
///|
/// 将 LucentContent 数组拼接为纯文本(Text 块拼接,其他块忽略)
pub fn lucent_content_to_text(parts : Array[LucentContent]) -> String {
let buf : Array[String] = []
for part in parts {
match part {
Text(s, _) => buf.push(s)
_ => ()
}
}
buf.join("")
}
///|
/// 检测并处理 reasoning dual_carrier 场景
///
/// 当消息级 reasoning(vLLM/DeepSeek)与 content 中的 Thinking 块并存时,
/// 优先使用消息级 reasoning,跳过 content Thinking 块,并记录 Degraded 诊断。
///
/// 用法:在适配器编码循环中,对每个 content 块调用此函数:
/// ```
/// if skip_thinking_when_dual_carrier(content[i], reasoning, diagnostics) {
/// i = i + 1
/// continue
/// }
/// ```
///
/// 返回 `true` 表示当前块应跳过(Thinking 块 + reasoning 已存在)。
pub fn skip_thinking_when_dual_carrier(
content : LucentContent,
reasoning : LucentThinking?,
diagnostics : Array[ConversionDiagnostic],
) -> Bool {
match content {
Thinking(_) =>
match reasoning {
Some(_) => {
diagnostics.push(
ConversionDiagnostic::degraded(
"reasoning.dual_carrier",
Some(
"both message.reasoning and content Thinking present; using message.reasoning",
),
),
)
true
}
None => false
}
_ => false
}
}
///|
/// 构造 unsupported content 诊断并推入诊断数组
///
/// 统一 `ConversionDiagnostic::unsupported("content.xxx", Some("..."))` 模式。
/// 适配器编码循环中遇到不支持的内容类型时调用。
///
/// 用法:
/// ```
/// push_unsupported_content("audio", "openai-chat", diagnostics)
/// // 生成: unsupported("content.audio", "audio content not supported by openai-chat")
/// ```
pub fn push_unsupported_content(
type_name : String,
adapter : String,
diagnostics : Array[ConversionDiagnostic],
) -> Unit {
diagnostics.push(
ConversionDiagnostic::unsupported(
"content." + type_name,
Some(type_name + " content not supported by " + adapter),
),
)
}
///|
/// 构造 unsupported feature 诊断并推入诊断数组
///
/// 更通用的版本,用于非 content 类型的 unsupported 诊断(如 tools.kind, options.store 等)。
///
/// 用法:
/// ```
/// push_unsupported("tools.kind", "tool kind not supported by gemini", diagnostics)
/// ```
pub fn push_unsupported(
field : String,
detail : String,
diagnostics : Array[ConversionDiagnostic],
) -> Unit {
diagnostics.push(ConversionDiagnostic::unsupported(field, Some(detail)))
}