///|
/// ═══ 功能组:facade(对外入口)═══
/// Prism 门面类型 + PrismOptions/ConversionMeta/DecodedResponse。
/// 只做转发,编码/解码实现见 pipeline.mbt。
///|
/// SDK 默认 provider 名称(Prism::new() 使用)
pub const DEFAULT_PROVIDER : String = "openai"
///|
/// SDK 默认模型名称(请求编码时的 fallback)
pub const DEFAULT_MODEL : String = "gpt-4o"
///|
/// ConversionMeta — 转换来源元信息(D8)
///
/// 回答"这个 LUX 值是从哪个协议解码来的",并附带转换损失诊断
/// 与厂商特有字段(provider_payload / extra_fields)。
/// 开发者可用 `source_provider` 做按需特判(if 分支),主路径代码仍只写一次。
pub struct ConversionMeta {
source_provider : String
diagnostics : Array[@lux.ConversionDiagnostic]
provider_payload : Json?
extra_fields : Map[String, Json]?
}
///|
pub fn ConversionMeta::new(
source_provider : String,
diagnostics : Array[@lux.ConversionDiagnostic],
provider_payload : Json?,
extra_fields : Map[String, Json]?,
) -> ConversionMeta {
{ source_provider, diagnostics, provider_payload, extra_fields, }
}
///|
/// DecodedResponse — 解码结果:主路径文本 + 溯源元信息
pub struct DecodedResponse {
text : String
meta : ConversionMeta
}
///|
pub fn DecodedResponse::new(
text : String,
meta : ConversionMeta,
) -> DecodedResponse {
{ text, meta, }
}
///|
/// PrismOptions — SDK 请求选项
///
/// 字段分两组:
/// - 基础五项(model/temperature/max_tokens/store/extras):`new()` 位置参数
/// - T3 扩展项(tool_choice/reasoning/structured_output/parallel_tool_calls):
/// 全部可选,经 `with_*` builder 设置;缺省 None 时编码输出与扩展前
/// 逐字节一致(有回归门禁测试)。
pub struct PrismOptions {
model : String?
temperature : Double?
max_tokens : Int?
store : Bool?
extras : Map[String, Json]?
/// 工具选择(IR 标准字段:Auto/None/Required/SpecificTool)
tool_choice : @lux.LucentToolChoice?
/// 推理开关(None = 不表达;Some(true/false) 显式声明)
reasoning_enabled : Bool?
/// 推理力度(设置即隐含启用,除非 reasoning_enabled=Some(false))
reasoning_effort : @lux.LucentReasoningEffort?
/// 结构化输出(IR 标准字段:JsonObject/Text/JsonSchema)
structured_output : @lux.LucentStructuredOutput?
/// 并行工具调用;经 extras 语义反传给各 provider
/// (messages: disable_parallel_tool_use;openai: parallel_tool_calls)
parallel_tool_calls : Bool?
}
///|
pub fn PrismOptions::default() -> PrismOptions {
{
model: None,
temperature: None,
max_tokens: None,
store: None,
extras: None,
tool_choice: None,
reasoning_enabled: None,
reasoning_effort: None,
structured_output: None,
parallel_tool_calls: None,
}
}
///|
pub fn PrismOptions::new(
model : String?,
temperature : Double?,
max_tokens : Int?,
store : Bool?,
extras : Map[String, Json]?,
) -> PrismOptions {
{
model,
temperature,
max_tokens,
store,
extras,
tool_choice: None,
reasoning_enabled: None,
reasoning_effort: None,
structured_output: None,
parallel_tool_calls: None,
}
}
///|
/// 设置工具选择(builder 风格,其余字段保留)
pub fn PrismOptions::with_tool_choice(
self : PrismOptions,
choice : @lux.LucentToolChoice,
) -> PrismOptions {
{ ..self, tool_choice: Some(choice), }
}
///|
/// 设置推理配置(enabled=None 时仅设置 effort,隐含启用)
pub fn PrismOptions::with_reasoning(
self : PrismOptions,
enabled : Bool?,
effort : @lux.LucentReasoningEffort?,
) -> PrismOptions {
{ ..self, reasoning_enabled: enabled, reasoning_effort: effort, }
}
///|
/// 设置结构化输出
pub fn PrismOptions::with_structured_output(
self : PrismOptions,
output : @lux.LucentStructuredOutput,
) -> PrismOptions {
{ ..self, structured_output: Some(output), }
}
///|
/// 设置并行工具调用(经 extras 语义反传,见字段注释)
pub fn PrismOptions::with_parallel_tool_calls(
self : PrismOptions,
enabled : Bool,
) -> PrismOptions {
{ ..self, parallel_tool_calls: Some(enabled), }
}
///|
/// Prism — 主实例
pub struct Prism {
provider : String
}
///|
pub fn Prism::new() -> Prism {
{ provider: DEFAULT_PROVIDER, }
}
///|
/// 设置 provider 名称
pub fn Prism::with_provider(_self : Prism, name : String) -> Prism {
{ provider: name, }
}
///|
/// 将文本请求编码为提供商 JSON(L1:文本进,JSON 出)
pub fn Prism::encode_request(
self : Prism,
text : String,
opts : PrismOptions,
) -> Result[String, String] {
self.encode_lux_request(build_request_from_text(text, opts))
}
///|
/// 将提供商 JSON 响应解码为纯文本(L1:JSON 进,文本出)
pub fn Prism::decode_response(
self : Prism,
json_str : String,
) -> Result[String, String] {
match self.decode_to_lux(json_str) {
Ok(resp) => Ok(resp.get_text())
Err(e) => Err(e)
}
}
///|
/// 运行时解析的规范 provider 名(精确/别名/模型模式命中后的主名称)
fn Prism::resolved_provider_name(self : Prism) -> String {
match match_provider_name(self.provider) {
Some(reg) => reg.name
None => self.provider
}
}
///|
/// 把 String 错误升级为带 provider 的 PrismError(类型化入口共用)
fn Prism::to_typed_error(self : Prism, message : String) -> PrismError {
PrismError::from_string(message).with_provider(
Some(self.resolved_provider_name()),
)
}
///|
/// 文本请求编码(类型化错误版;成功路径与 encode_request 一致)
pub fn Prism::encode_request_typed(
self : Prism,
text : String,
opts : PrismOptions,
) -> Result[String, PrismError] {
match self.encode_request(text, opts) {
Ok(json) => Ok(json)
Err(e) => Err(self.to_typed_error(e))
}
}
///|
/// 响应解码为 DecodedResponse(类型化错误版)
pub fn Prism::decode_response_typed(
self : Prism,
json_str : String,
) -> Result[DecodedResponse, PrismError] {
match self.decode_response_with_meta(json_str) {
Ok(v) => Ok(v)
Err(e) => Err(self.to_typed_error(e))
}
}
///|
/// SSE 解码为事件数组(类型化错误版)
pub fn Prism::decode_sse_typed(
self : Prism,
sse_str : String,
) -> Result[Array[PrismEvent], PrismError] {
match self.decode_sse(sse_str) {
Ok(events) => Ok(events)
Err(e) => Err(self.to_typed_error(e))
}
}
///|
/// 将提供商 JSON 响应解码为 DecodedResponse(文本 + 来源元信息)。
///
/// meta.source_provider 为运行时从注册表解析的规范 provider 名
/// (精确匹配/别名/模型正则),开发者可据此做按需特判。
pub fn Prism::decode_response_with_meta(
self : Prism,
json_str : String,
) -> Result[DecodedResponse, String] {
let source = match match_provider_name(self.provider) {
Some(reg) => reg.name
None => self.provider
}
match self.decode_to_lux(json_str) {
Ok(resp) => {
let meta = ConversionMeta::new(source, [], resp.provider_payload, None)
Ok(DecodedResponse::new(resp.get_text(), meta))
}
Err(e) => Err(e)
}
}
///|
/// 将提供商响应解码为完整 LucentResponse,并返回结构化校验诊断。
pub fn Prism::decode_response_with_diagnostics(
self : Prism,
json_str : String,
) -> Result[@lux.ConversionResult[@lux.LucentResponse], String] {
match self.decode_to_lux(json_str) {
Ok(resp) => {
let result = @lux.ConversionResult::new(resp)
match get_schema(self.provider) {
Some(schema) => {
let mut checked = result
for error in schema.validate_response(resp) {
checked = checked.with_diagnostic(
@lux.ConversionDiagnostic::new("response", Invalid, Some(error)),
)
}
Ok(checked)
}
None => Ok(result)
}
}
Err(e) => Err(e)
}
}
///|
/// 将文本请求编码为提供商 JSON,并返回结构化校验诊断。
pub fn Prism::encode_request_with_diagnostics(
self : Prism,
text : String,
opts : PrismOptions,
) -> Result[@lux.ConversionResult[String], String] {
let req = build_request_from_text(text, opts)
let mut result = @lux.ConversionResult::new(req)
match get_schema(self.provider) {
Some(schema) =>
for error in schema.validate_request(req) {
result = result.with_diagnostic(
@lux.ConversionDiagnostic::new("request", Invalid, Some(error)),
)
}
None => ()
}
// capability 消费:请求所需能力 vs provider 声明(iteration-008 方向 A)
for diagnostic in self.check_capability(result.value()) {
result = result.with_diagnostic(diagnostic)
}
match self.encode_lux_request(result.value()) {
Ok(json) => {
let mut encoded = @lux.ConversionResult::new(json)
for diagnostic in result.diagnostics() {
encoded = encoded.with_diagnostic(diagnostic)
}
Ok(encoded)
}
Err(e) => Err(e)
}
}
///|
/// === 流式接口 ===
///|
/// 将文本请求编码为流式提供商 JSON
pub fn Prism::encode_stream_request(
self : Prism,
text : String,
opts : PrismOptions,
) -> Result[String, String] {
let mut req = build_request_from_text(text, opts)
req = @lux.LucentRequest::with_stream(req, true)
self.encode_lux_request(req)
}
///|
/// 将提供商 SSE 事件解码为 PrismEvent 数组
pub fn Prism::decode_sse(
self : Prism,
sse_str : String,
) -> Result[Array[PrismEvent], String] {
let provider = self.provider
match decode_sse_inner(provider, sse_str) {
Ok(raw_events) => Ok(lucent_events_to_prism(raw_events))
Err(e) => Err(e)
}
}
///|
/// L1 零配置:文本进、文本出(HTTP 由 Host 注入 send)
pub fn Prism::complete(
self : Prism,
text : String,
opts : PrismOptions,
send : (String) -> Result[String, String],
) -> Result[String, String] {
match self.encode_request(text, opts) {
Ok(json) =>
match send(json) {
Ok(resp_json) => self.decode_response(resp_json)
Err(e) => Err(e)
}
Err(e) => Err(e)
}
}
///|
/// L2 Agent 循环:流式请求 + 5 事件解码(HTTP 由 Host 注入 send)
pub fn Prism::stream(
self : Prism,
ctx : Context,
send : (String) -> Result[String, String],
) -> Result[Array[PrismEvent], String] {
let model = DEFAULT_MODEL
let mut req = context_to_lux_request(ctx, model, PrismOptions::default())
req = @lux.LucentRequest::with_stream(req, true)
match self.encode_lux_request(req) {
Ok(json) =>
match send(json) {
Ok(sse) => self.decode_sse(sse)
Err(e) => Err(e)
}
Err(e) => Err(e)
}
}
///|
/// Agent 循环回填闭环:把流事件累积为 assistant 回合并追加到 Context
///
/// 用法(与 add_tool_result 配对):
/// ```moonbit nocheck
/// let events = prism.stream(ctx, send)?
/// let ctx = prism.events_to_context(ctx, events) // 回填 assistant 回合
/// // 执行工具…
/// let ctx = ctx.add_tool_result(id, result, false) // 注入结果
/// // 第二轮请求(provider 不再拒绝 tool_result)
/// ```
///
/// 语义:TextDelta 合并为一个 Text 块;每个 ToolCall 事件产生一个 ToolUse
/// 块(含累积后的完整 arguments_json);Thinking 不回填(v1)。
/// 空回合(无文本无工具调用)不追加消息。
pub fn Prism::events_to_context(
_self : Prism,
ctx : Context,
events : Array[PrismEvent],
) -> Context {
let mut text = ""
let tool_calls : Array[SdkToolCall] = []
for ev in events {
match ev {
TextDelta(s) => text = text + s
ToolCall(tc) =>
tool_calls.push(SdkToolCall::new(tc.id, tc.name, tc.arguments_json))
Thinking(_) => ()
ToolResult(_) => ()
Finish(_) => ()
}
}
if text == "" && tool_calls.length() == 0 {
ctx
} else {
ctx.add_assistant_turn(text, tool_calls)
}
}
///|
/// === 能力查询 ===
///|
/// SdkProviderEndpoint — 部署态端点配置投影(T6)
///
/// Extension 字段:全部可选。SDK 不发起 HTTP(宿主注入 send),
/// 端点配置供宿主路由/鉴权读取;None 表示该 provider 未声明。
pub struct SdkProviderEndpoint {
base_url : String?
auth_header : String?
api_version : String?
}
///|
/// 查询指定 provider 的部署态端点配置(基于注册表,含动态注册)
pub fn Prism::endpoint(
_self : Prism,
provider_name : String,
) -> SdkProviderEndpoint? {
match match_provider_name(provider_name) {
Some(reg) =>
Some({
base_url: reg.base_url,
auth_header: reg.auth_header,
api_version: reg.api_version,
})
None => None
}
}
///|
/// 查询指定 provider 的能力声明(基于注册表)
pub fn Prism::capability(
_self : Prism,
provider_name : String,
) -> @lux.ProviderCapability? {
match match_provider_name(provider_name) {
Some(reg) => Some(reg.capability)
None => None
}
}
///|
/// 能力校验(capability 消费落地,iteration-008 架构审计方向 A)
///
/// 将请求所需能力与 provider 的 `ProviderCapability` 声明对照,
/// 声明为 `Absent` 的能力若请求实际使用,产生 `Unsupported` 诊断——
/// 让能力声明从"死数据"变为运行时契约(呼应 AGENTS.md
/// "Unsupported capabilities MUST fail explicitly")。
///
/// 检查项:
/// - 请求含 `tools` 但声明 `tool_calling=Absent` → Unsupported
/// - 请求启用 `reasoning` 但声明 `reasoning=Absent` → Unsupported
/// - 请求含 `structured_output` 但声明 `structured_output=Absent` → Unsupported
///
/// 空数组表示请求能力全部被支持。
pub fn Prism::check_capability(
self : Prism,
req : @lux.LucentRequest,
) -> Array[@lux.ConversionDiagnostic] {
let diagnostics : Array[@lux.ConversionDiagnostic] = []
match match_provider_name(self.provider) {
Some(reg) => {
let caps = reg.capability.capabilities
// 1. 工具调用
match req.tools {
Some(tools) if tools.length() > 0 =>
if caps.tool_calling == Absent {
diagnostics.push(
@lux.ConversionDiagnostic::unsupported(
"request.tools",
Some(self.provider + " declares tool_calling=none"),
),
)
}
_ => ()
}
// 2. 推理
match req.reasoning {
Some(rc) if rc.enabled =>
if caps.reasoning == Absent {
diagnostics.push(
@lux.ConversionDiagnostic::unsupported(
"request.reasoning",
Some(self.provider + " declares reasoning=none"),
),
)
}
_ => ()
}
// 3. 结构化输出
match req.options.structured_output {
Some(_) =>
if caps.structured_output == Absent {
diagnostics.push(
@lux.ConversionDiagnostic::unsupported(
"request.options.structured_output",
Some(self.provider + " declares structured_output=none"),
),
)
}
None => ()
}
}
None => ()
}
diagnostics
}