///|
/// A minimal wrapper for OpenAI's Responses API (`/responses`), a newer
/// unified endpoint that accepts a single `input` (string or message list)
/// and returns an `output` array of items.
///
/// This covers the common text-in/text-out case; the full Responses API has a
/// much larger surface (tools, state, streaming) that a real client would
/// extend over time.
pub(all) struct ResponseRequest {
model : String
input : String
mut instructions : String?
mut max_output_tokens : Int?
mut temperature : Double?
}
///|
/// Create a Responses request with a plain-text input.
pub fn ResponseRequest::new(model : String, input : String) -> ResponseRequest {
{
model,
input,
instructions: None,
max_output_tokens: None,
temperature: None,
}
}
///|
/// Set top-level instructions (akin to a system prompt).
pub fn ResponseRequest::instructions(
self : ResponseRequest,
text : String,
) -> ResponseRequest {
self.instructions = Some(text)
self
}
///|
pub fn ResponseRequest::max_output_tokens(
self : ResponseRequest,
n : Int,
) -> ResponseRequest {
self.max_output_tokens = Some(n)
self
}
///|
pub fn ResponseRequest::temperature(
self : ResponseRequest,
t : Double,
) -> ResponseRequest {
self.temperature = Some(t)
self
}
///|
pub impl ToJson for ResponseRequest with fn to_json(self : ResponseRequest) -> Json {
let obj : Map[String, Json] = {
"model": Json::string(self.model),
"input": Json::string(self.input),
}
if self.instructions is Some(i) {
obj["instructions"] = Json::string(i)
}
if self.max_output_tokens is Some(n) {
obj["max_output_tokens"] = Json::number(n.to_double())
}
if self.temperature is Some(t) {
obj["temperature"] = Json::number(t)
}
Json::object(obj)
}
///|
/// A response from the `/responses` endpoint.
pub(all) struct ResponseResult {
id : String
model : String
status : String
output_text : String
usage : Usage?
} derive(Debug)
///|
pub impl @json.FromJson for ResponseResult with fn from_json(
json : Json,
path : @json.JsonPath,
) -> ResponseResult {
guard json is Object(obj) else {
raise @json.JsonDecodeError((path, "ResponseResult: expected object"))
}
let id = match obj.get("id") {
Some(String(s)) => s
_ => ""
}
let model = match obj.get("model") {
Some(String(s)) => s
_ => ""
}
let status = match obj.get("status") {
Some(String(s)) => s
_ => ""
}
// Prefer the convenience `output_text` field when present; otherwise walk
// the `output` array and concatenate text content.
let output_text = match obj.get("output_text") {
Some(String(s)) => s
_ => extract_output_text(obj.get("output"))
}
let usage = match obj.get("usage") {
Some(Object(_) as u) => Some(@json.from_json(u))
_ => None
}
{ id, model, status, output_text, usage }
}
///|
/// Walk a Responses `output` array, concatenating the text of message items.
fn extract_output_text(output : Json?) -> String {
let buf = StringBuilder::new()
guard output is Some(Array(items)) else { return "" }
for item in items {
guard item is Object(it) else { continue }
guard it.get("content") is Some(Array(contents)) else { continue }
for c in contents {
guard c is Object(cc) else { continue }
match cc.get("text") {
Some(String(t)) => buf.write_string(t)
_ => ()
}
}
}
buf.to_string()
}
///|
/// Perform a request against the Responses API (`POST /responses`).
pub async fn Client::respond(
self : Client,
request : ResponseRequest,
) -> ResponseResult raise LLMError {
let json = self.post_json("/responses", request.to_json())
@json.from_json(json) catch {
err => raise Decode(err.to_string())
}
}