///|
/// ═══ 功能组:hooks(IR 策略层,T4)═══
/// IR→IR 纯函数钩子 + convert_*_with_hooks 组合入口 + 内置常用钩子。
///
/// 定位(架构审计 P0-6):IR 不只是转换中间站,更是 middleware 的 policy
/// 落点。此前 convert_* 是 decode→encode 直通,宿主无法在 IR 层插入
/// 审计/脱敏/改写/裁剪逻辑,只能在 Prism 外重新 parse JSON——IR 价值被浪费。
///
/// 护栏(架构文档 §演进护栏):
/// - 钩子必须是 IR→IR 纯函数,只依赖 lux 类型;禁止反向依赖 provider
/// - 造成数据损失的操作(裁工具、压上限)必须 push Degraded 诊断,
/// 遵守"Unsupported capabilities MUST fail explicitly, never silently drop"
/// - 不传 hooks(空数组)时行为与 convert_* 完全一致
///|
/// SdkRequestHook — 请求 IR 钩子
///
/// 入参:(IR, 诊断收集器);返回变换后的 IR。钩子可向收集器 push
/// ConversionDiagnostic,最终与 decode/encode 两阶段诊断合并返回。
pub type SdkRequestHook = (@lux.LucentRequest, Array[@lux.ConversionDiagnostic]) -> @lux.LucentRequest
///|
/// SdkResponseHook — 响应 IR 钩子(同上)
pub type SdkResponseHook = (
@lux.LucentResponse,
Array[@lux.ConversionDiagnostic],
) -> @lux.LucentResponse
///|
/// SdkStreamHook — 流事件 IR 钩子(事件数组整体变换)
pub type SdkStreamHook = (
Array[@lux.LucentStreamEvent],
Array[@lux.ConversionDiagnostic],
) -> Array[@lux.LucentStreamEvent]
///|
/// 合并三段诊断:以编码结果为主体,并入解码诊断与钩子诊断
fn merge_hook_diagnostics(
encoded : @lux.ConversionResult[String],
decode_diags : Array[@lux.ConversionDiagnostic],
hook_diags : Array[@lux.ConversionDiagnostic],
) -> @lux.ConversionResult[String] {
encoded.with_diagnostics(decode_diags).with_diagnostics(hook_diags)
}
///|
/// convert_request_with_hooks — 请求转换 + IR 钩子链(场景 2 策略注入)
///
/// 管线:source decode → hooks(IR) → target encode;诊断按
/// decode/hook/encode 三段合并。hooks 为空数组时与 convert_request 一致。
pub fn convert_request_with_hooks(
source : String,
json_str : String,
target : String,
hooks : Array[SdkRequestHook],
) -> Result[@lux.ConversionResult[String], String] {
match resolve_pair(source, target) {
Ok((src, dst)) =>
match (src.request_decode)(json_str) {
Ok(dcr) => {
let hook_diags : Array[@lux.ConversionDiagnostic] = []
let mut ir = dcr.value()
for hook in hooks {
ir = hook(ir, hook_diags)
}
match (dst.request_encode)(ir) {
Ok(ecr) =>
Ok(merge_hook_diagnostics(ecr, dcr.diagnostics(), hook_diags))
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
Err(e) => Err(e)
}
}
///|
/// convert_response_with_hooks — 响应转换 + IR 钩子链
pub fn convert_response_with_hooks(
source : String,
json_str : String,
target : String,
hooks : Array[SdkResponseHook],
) -> Result[@lux.ConversionResult[String], String] {
match resolve_pair(source, target) {
Ok((src, dst)) =>
match (src.response_decode)(json_str) {
Ok(dcr) => {
let hook_diags : Array[@lux.ConversionDiagnostic] = []
let mut ir = dcr.value()
for hook in hooks {
ir = hook(ir, hook_diags)
}
match (dst.response_encode)(ir) {
Ok(ecr) =>
Ok(merge_hook_diagnostics(ecr, dcr.diagnostics(), hook_diags))
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
Err(e) => Err(e)
}
}
///|
/// convert_stream_with_hooks — 流转换 + IR 钩子链(事件级)
///
/// 钩子作用于整个事件数组(可做事件过滤/注入/重排);
/// 空 hooks 时与 convert_stream 一致。
pub fn convert_stream_with_hooks(
source : String,
sse_str : String,
target : String,
hooks : Array[SdkStreamHook],
) -> Result[@lux.ConversionResult[String], String] {
match resolve_pair(source, target) {
Ok((src, dst)) =>
match (src.events_decode)(sse_str) {
Ok(dcr) => {
let hook_diags : Array[@lux.ConversionDiagnostic] = []
let mut events = dcr.value()
for hook in hooks {
events = hook(events, hook_diags)
}
match (dst.events_encode)(events) {
Ok(ecr) =>
Ok(merge_hook_diagnostics(ecr, dcr.diagnostics(), hook_diags))
Err(e) => Err(e)
}
}
Err(e) => Err(e)
}
Err(e) => Err(e)
}
}
///|
/// ═══ 内置常用钩子(middleware 高频策略开箱即用)═══
///|
/// 钳制 max_output_tokens 上限(成本兜底)
///
/// 实际压价时 push Degraded 诊断(值被改小才算损失);
/// 原值小于等于 limit 时原样返回、无诊断。
pub fn hook_clamp_max_tokens(limit : Int) -> SdkRequestHook {
fn(
ir : @lux.LucentRequest,
diags : Array[@lux.ConversionDiagnostic],
) -> @lux.LucentRequest {
match ir.options.max_output_tokens {
Some(cur) if cur > limit => {
diags.push(
@lux.ConversionDiagnostic::degraded(
"options.max_output_tokens",
Some("clamped from \{cur} to \{limit} by hook"),
),
)
let opts = @lux.LucentOptions::new(
ir.options.temperature,
ir.options.top_p,
ir.options.top_k,
Some(limit),
ir.options.stop,
ir.options.stream,
ir.options.candidate_count,
ir.options.structured_output,
ir.options.store,
ir.options.extras,
)
@lux.LucentRequest::new(
ir.model,
ir.instructions,
ir.conversation,
ir.tools,
ir.tool_choice,
opts,
ir.capabilities,
ir.reasoning,
ir.metadata,
ir.extra,
)
}
_ => ir
}
}
}
///|
/// 工具白名单过滤(裁剪越权工具)
///
/// 被裁掉的工具 push Degraded 诊断(数据不静默丢失);
/// 全部保留时无诊断。allowed 为空数组 = 裁掉所有工具。
pub fn hook_filter_tools(allowed : Array[String]) -> SdkRequestHook {
fn(
ir : @lux.LucentRequest,
diags : Array[@lux.ConversionDiagnostic],
) -> @lux.LucentRequest {
match ir.tools {
Some(tools) =>
if tools.length() == 0 {
ir
} else {
let kept : Array[@lux.LucentTool] = []
let mut dropped = 0
for t in tools {
if allowed.contains(t.name) {
kept.push(t)
} else {
dropped = dropped + 1
}
}
if dropped == 0 {
ir
} else {
diags.push(
@lux.ConversionDiagnostic::degraded(
"tools",
Some("\{dropped} tool(s) filtered by allowlist hook"),
),
)
@lux.LucentRequest::new(
ir.model,
ir.instructions,
ir.conversation,
Some(kept),
ir.tool_choice,
ir.options,
ir.capabilities,
ir.reasoning,
ir.metadata,
ir.extra,
)
}
}
None => ir
}
}
}
///|
/// 追加系统指令(prompt 注入/改写)
///
/// 有意为之的策略操作,不产生诊断(非损失)。
pub fn hook_append_instructions(text : String) -> SdkRequestHook {
fn(
ir : @lux.LucentRequest,
_diags : Array[@lux.ConversionDiagnostic],
) -> @lux.LucentRequest {
let appended : Array[@lux.LucentContent] = match ir.instructions {
Some(existing) => {
let arr = existing.copy()
arr.push(@lux.LucentContent::text(text))
arr
}
None => [@lux.LucentContent::text(text)]
}
@lux.LucentRequest::new(
ir.model,
Some(appended),
ir.conversation,
ir.tools,
ir.tool_choice,
ir.options,
ir.capabilities,
ir.reasoning,
ir.metadata,
ir.extra,
)
}
}
///|
/// 改写模型名(路由/降级:把请求打到指定后端模型)
///
/// 有意为之的路由操作,不产生诊断;同名时无操作。
pub fn hook_set_model(model : String) -> SdkRequestHook {
fn(
ir : @lux.LucentRequest,
_diags : Array[@lux.ConversionDiagnostic],
) -> @lux.LucentRequest {
if ir.model == model {
ir
} else {
@lux.LucentRequest::new(
model,
ir.instructions,
ir.conversation,
ir.tools,
ir.tool_choice,
ir.options,
ir.capabilities,
ir.reasoning,
ir.metadata,
ir.extra,
)
}
}
}
///|
/// 流钩子示例:丢弃 Error 事件后的所有事件(故障演练/熔断演示用)
///
/// 保留错误事件本身(显式失败原则),只丢弃其后的噪音。
pub fn hook_stream_drop_after_error() -> SdkStreamHook {
fn(
events : Array[@lux.LucentStreamEvent],
_diags : Array[@lux.ConversionDiagnostic],
) -> Array[@lux.LucentStreamEvent] {
let result : Array[@lux.LucentStreamEvent] = []
for ev in events {
result.push(ev)
match ev {
Error(_) => break
_ => ()
}
}
result
}
}