commit - 80d01e8e19c1b9b7f13401442cd44218746a79ca
commit + c5b95489c75049dee80844b53e706be704929dfb
blob - c518f977566828fb20eba8b47b4db91a7070735b
blob + e7752d5851ce6dd49241bb19fd098610f22b89ae
--- src/cli.rs
+++ src/cli.rs
use crate::openai::{Message, ReasoningEffort};
use clap::{
- arg, builder::PossibleValue, command, value_parser, ArgGroup, ArgMatches, Command, ValueEnum,
+ ArgGroup, ArgMatches, Command, ValueEnum, arg, builder::PossibleValue, command, value_parser,
};
use derive_builder::Builder;
pub api_key: Option<String>,
pub messages: Vec<Message>,
pub model: String,
- pub temperature: f32,
+ #[builder(default)]
+ pub temperature: Option<f32>,
pub output_format: OutputFormat,
pub file: String,
pub timeout: Duration,
/// Top-level command
fn cli() -> Command {
command!()
- .arg(arg!(model: -m --model <MODEL> "Sets model. See https://platform.openai.com/docs/models for model identifiers.").default_value("gpt-5"))
+ .arg(arg!(model: -m --model <MODEL> "Sets model. See https://platform.openai.com/docs/models for model identifiers.").default_value("gpt-5.5"))
.arg(
arg!(temperature: -t --temperature <TEMP> "Sets temperature")
- .value_parser(value_parser!(f32))
- .default_value("0.0"),
+ .value_parser(value_parser!(f32)),
)
.arg(
arg!(timeout: -T --timeout <DURATION> "Sets timeout duration in seconds")
.expect("Models is required")
.to_string();
- let temperature = *matches
- .get_one::<f32>("temperature")
- .expect("Temperature is required");
+ let temperature = matches.get_one::<f32>("temperature").copied();
let timeout = matches
.get_one::<u64>("timeout")
Message::assistant("ROBOT"),
Message::user("USER2"),
],
- "system message is always brought to front, followed by assistant and user messages in order");
+ "system message is always brought to front, followed by assistant and user messages in order"
+ );
Ok(())
}
blob - 385cb933f50e88ff173b340a8a8252a807509bab
blob + 076c7e1b35e95e97574dc4be3b457573adc05731
--- src/exec/chat.rs
+++ src/exec/chat.rs
//! Implements chat subcommand
+use crate::Error;
use crate::cli::{Invocation, OutputFormat};
use crate::openai::{self, FinishReason, Message, Reasoning, Response};
use crate::parse;
-use crate::Error;
use anyhow::{Context, Result};
use std::fs::File;
return Err(Error::UnexpectedResponse(format!(
"Unexpected number of choices in response: {:?}",
resp
- )))
+ )));
}
};
return Err(Error::UnexpectedResponse(format!(
"Received unrecognized stop reason for choice: {:?}",
choice
- )))
+ )));
}
}
.api_key(Some(String::default()))
.messages(vec![])
.model(String::default())
- .temperature(1.0)
+ .temperature(Some(1.0))
.output_format(OutputFormat::Plaintext)
.timeout(Duration::from_secs(10))
.file("-".to_string())
blob - e2497fbce7911cb9f73c40389e1cb841a474879b
blob + e0e9bedd15e252c826cee3ae5507c2a1bba259a3
--- src/openai.rs
+++ src/openai.rs
use derive_builder::Builder;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
-use serde_json::{json, Value};
+use serde_json::{Value, json};
/// Convienience Client for OpenAI Responses API
pub struct Client {
pub struct ResponseRequest {
model: String,
messages: Vec<Message>,
- temperature: f32,
+ #[builder(default)]
+ temperature: Option<f32>,
timeout: Duration,
#[builder(default)]
reasoning: Option<Reasoning>,
json!({
"role": self.role.as_str(),
"content": [{
- "type": "text",
+ "type": "input_text",
"text": self.content.clone(),
}]
})
let mut payload = json!({
"model": self.model,
"input": input,
- "temperature": self.temperature,
});
+ if let Some(temperature) = self.temperature
+ && let Some(obj) = payload.as_object_mut()
+ {
+ obj.insert("temperature".to_string(), json!(temperature));
+ }
+
if let Some(reasoning) = &self.reasoning
- && let Some(obj) = payload.as_object_mut() {
- obj.insert(
- "reasoning".to_string(),
- serde_json::to_value(reasoning).expect("failed to serialize reasoning"),
- );
- }
+ && let Some(obj) = payload.as_object_mut()
+ {
+ obj.insert(
+ "reasoning".to_string(),
+ serde_json::to_value(reasoning).expect("failed to serialize reasoning"),
+ );
+ }
payload
}
#[derive(Debug, Deserialize)]
struct ResponsesAPIResponse {
+ #[serde(rename = "created_at", alias = "created")]
#[serde(with = "ts_seconds")]
created: DateTime<Utc>,
model: String,
let request = ResponseRequest::builder()
.model("gpt-5".to_string())
.messages(vec![Message::user("Hello")])
- .temperature(0.0)
+ .temperature(Some(0.0))
.timeout(Duration::from_secs(30))
.reasoning(Some(Reasoning::from_effort(ReasoningEffort::High)))
.build()
assert_eq!(payload["reasoning"]["effort"], "high");
assert_eq!(payload["model"], "gpt-5");
+ assert_eq!(payload["temperature"], 0.0);
Ok(())
}
let request = ResponseRequest::builder()
.model("gpt-5".to_string())
.messages(vec![Message::user("Hello")])
- .temperature(0.0)
.timeout(Duration::from_secs(30))
.build()
.expect("request builds");
let payload = request.to_payload();
assert!(payload.get("reasoning").is_none());
+ assert!(payload.get("temperature").is_none());
Ok(())
}
blob - 05b4e04c7c77c264b5006ba51802f1006384b482
blob + b5bfc88ba906ba1e4d7e8addfd1917c584906417
--- tests/chat.rs
+++ tests/chat.rs
.with_header("content-type", "application/json")
.with_header("authorization", "Bearer ABCDE")
.match_body(mockito::Matcher::PartialJson(json!({
- "model": "gpt-5",
+ "model": "gpt-5.5",
"input": [{
"role": "user",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "Hello"
}]
}]
r#"{
"id": "resp_XXXXX",
"created": 1688413145,
- "model": "gpt-5",
+ "model": "gpt-5.5",
"output": [{
"id": "msg_XXXXX",
"type": "message",
.with_header("content-type", "application/json")
.with_header("authorization", "Bearer ABCDE")
.match_body(mockito::Matcher::PartialJson(json!({
- "model": "gpt-5",
+ "model": "gpt-5.5",
"input": [{
"role": "user",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "Hello"
}]
}]
r#"{
"id": "resp_XXXXX",
"created": 1688413145,
- "model": "gpt-5",
+ "model": "gpt-5.5",
"output": [{
"id": "msg_XXXXX",
"type": "message",
.with_header("content-type", "application/json")
.with_header("authorization", "Bearer ABCDE")
.match_body(mockito::Matcher::PartialJson(json!({
- "model": "gpt-5",
+ "model": "gpt-5.5",
"reasoning": {
"effort": "medium"
},
"input": [{
"role": "user",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "Hello"
}]
}]
r#"{
"id": "resp_XXXXX",
"created": 1688413145,
- "model": "gpt-5",
+ "model": "gpt-5.5",
"output": [{
"id": "msg_XXXXX",
"type": "message",
.with_header("content-type", "application/json")
.with_header("authorization", "Bearer ABCDE")
.match_body(mockito::Matcher::PartialJson(json!({
- "model": "gpt-5",
+ "model": "gpt-5.5",
"input": [{
"role": "system",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "SYSTEM"
}],
}, {
"role": "user",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "USER_1"
}],
}, {
"role": "assistant",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "ASSI_1"
}],
}, {
"role": "user",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "USER_2"
}],
}, {
"role": "assistant",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "ASSI_2"
}],
}, {
"role": "user",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "USER_STDIN"
}],
}]
r#"{
"id": "resp_XXXXX",
"created": 1688413145,
- "model": "gpt-5",
+ "model": "gpt-5.5",
"output": [{
"id": "msg_XXXXX",
"type": "message",
.with_header("content-type", "application/json")
.with_header("authorization", "Bearer ABCDE")
.match_body(mockito::Matcher::PartialJson(json!({
- "model": "gpt-5",
+ "model": "gpt-5.5",
"input": [{
"role": "user",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "USER"
}],
}],
.with_header("content-type", "application/json")
.with_header("authorization", "Bearer ABCDE")
.match_body(mockito::Matcher::PartialJson(json!({
- "model": "gpt-5",
+ "model": "gpt-5.5",
"input": [{
"role": "user",
"content": [{
- "type": "text",
+ "type": "input_text",
"text": "Hello from file"
}],
}],
r#"{
"id": "resp_XXXXX",
"created": 1688413145,
- "model": "gpt-5",
+ "model": "gpt-5.5",
"output": [{
"id": "msg_XXXXX",
"type": "message",