// Helpers shared by the provider dialects: the parts of lifting/lowering
// that are genuinely provider-independent. Anything keyed by provider JSON
// key names stays inside each dialect.
///|
pub impl Show for DialectError with fn output(self, logger) {
match self {
MalformedReply(msg) => logger.write_string("malformed reply: \{msg}")
}
}
///|
/// Provider API errors are data on the message, never exceptions (pi
/// convention). Matches the `{"error": {"message": ...}}` shape all four
/// providers answer with (Anthropic wraps it in `{"type": "error", ...}`,
/// which this pattern also matches).
pub fn api_error(reply : Json, model~ : String) -> ContextMessage? {
guard reply is { "error": { "message": String(message), .. }, .. } else {
return None
}
Some(
AssistantMsg(
content=[],
stop=Errored,
model~,
response_id=None,
usage=None,
error=Some(message),
),
)
}
///|
/// What an image degrades to where a wire path takes text only — tool-result
/// content, which no provider models as an image block. User turns carry
/// images natively (see each dialect's user-content mapping).
fn image_marker(media_type : String) -> String {
"[image \{media_type} omitted]"
}
///|
/// Flatten user blocks to one text: text passes through, images degrade to
/// a marker (v1 wire formats are text-first).
pub fn user_text(blocks : Array[UserBlock]) -> String {
blocks
.map(block => {
match block {
UserText(text) => text
UserImage(media_type~, ..) => image_marker(media_type)
}
})
.join("\n\n")
}
///|
/// Which model answered, and what the provider called the call.
///
/// The tail of every `lift_message`, and identical in all of them: all four
/// providers name the model in `model` and the call in `id`, at the top level
/// of the reply. `fallback` is what the dialect was configured with, for a
/// provider that echoes neither.
///
/// This is here and the `usage` block above it is not, because usage is keyed
/// by provider JSON key names — `input_tokens` against `prompt_tokens` — and
/// this is not keyed by anything. That is the line the header draws.
pub fn reply_identity(reply : Json, fallback~ : String) -> (String, String?) {
let model = match reply {
{ "model": String(m), .. } => m
_ => fallback
}
let response_id = match reply {
{ "id": String(id), .. } => Some(id)
_ => None
}
(model, response_id)
}