#![doc = include_str!("../README.md")]
#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(all(target_family = "wasm", not(target_os = "unknown")))]
compile_error!(
"nanocodex-oai-api supports native targets and hosted wasm*-unknown-unknown targets; WASI is not yet supported"
);
pub mod auth;
pub mod events;
mod openai;
pub mod pricing;
pub mod responses;
pub mod session;
pub mod tools;
pub mod tower;
pub mod transport;
use std::{fmt, path::PathBuf, str::FromStr};
use serde::{Deserialize, Serialize};
pub(crate) use auth::{OpenAiAuth, OpenAiAuthError, OpenAiAuthMode, OpenAiAuthSnapshot};
pub(crate) use events::stream::EventSink;
pub(crate) use events::{
AgentEventData, AgentEventKind, AssistantEvent, ContextEvent, EventError, ModelEvent,
ReasoningEvent, RunEvent, ToolEvent, TransportEvent, monotonic_now_ns,
};
pub(crate) use openai::ModelConfig;
pub use openai::{OpenAi, OpenAiBuilder, OpenAiError};
pub(crate) use pricing::{CostStatus, EstimatedUsdCost};
pub use responses::ResponseEvent;
pub(crate) use responses::{
ContentItem, FunctionOutputBody, FunctionOutputContent, MessagePhase, MessageRole,
ResponseItem, ResponseItemId, ToolDefinition, Usage,
};
pub use session::{
CompletedResponse, Response, ResponseError, ResponseErrorKind, ResponseTurn, Session,
SessionBuildError, SessionBuilder,
};
pub(crate) use tools::ToolOutputBody;
pub(crate) use tower::attempt::{
ResponsesAttempt, ResponsesAttemptFactory, ResponsesOutput, ResponsesServiceResponse,
TransportStats,
};
pub(crate) use tower::service::ResponsesService;
pub(crate) use tower::{
DefaultResponsesService, ResponsesClient, ResponsesRetryPolicy, ResponsesServiceError,
};
pub(crate) use transport::EncodedRequest;
pub(crate) use transport::{ResponsesError, ResponsesHistory, ResponsesTransport, RetryAdvice};
pub(crate) use tower::{attempt, middleware, service, service_error, stream};
#[cfg(not(target_family = "wasm"))]
pub(crate) use transport::{connector, http};
pub(crate) use transport::{socket, telemetry};
#[doc(hidden)]
pub mod __private {
pub use crate::{
events::stream::EventSink,
openai::{
CallerServiceFactory, LayeredServiceFactory, ModelConfig, ResponsesServiceFactory,
},
session::{
context::{ContextManager, assign_missing_response_item_id},
state::{ManagedSessionState, ManagedSessionStateError},
},
tower::attempt::ResponsesAttemptFactory,
};
pub mod compaction {
pub use crate::session::compaction::{
auto_compact_token_limit, install_history, trigger,
trim_tool_outputs_to_fit_context_window,
};
}
pub fn into_openai_parts<F>(openai: crate::OpenAi<F>) -> (ModelConfig, F)
where
F: ResponsesServiceFactory,
{
openai.into_parts()
}
pub fn with_code_mode_tool_names(
profile: crate::responses::RequestProfile,
names: Vec<(String, String)>,
) -> crate::responses::RequestProfile {
profile.with_code_mode_tool_names(names)
}
}
pub const MODEL: &str = "gpt-5.6-sol";
pub const CONTEXT_WINDOW_TOKENS: u64 = 272_000;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Prompt {
pub instruction: PromptInput,
}
impl Prompt {
#[must_use]
pub fn new(instruction: impl Into<String>) -> Self {
Self {
instruction: PromptInput::Text(instruction.into()),
}
}
#[must_use]
pub fn content(input: impl IntoIterator<Item = UserInput>) -> Self {
Self {
instruction: PromptInput::Content(input.into_iter().collect()),
}
}
}
impl From<String> for Prompt {
fn from(instruction: String) -> Self {
Self::new(instruction)
}
}
impl From<&str> for Prompt {
fn from(instruction: &str) -> Self {
Self::new(instruction)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum PromptInput {
Text(String),
Content(Vec<UserInput>),
}
impl PromptInput {
#[must_use]
pub fn text_bytes(&self) -> usize {
match self {
Self::Text(text) => text.len(),
Self::Content(items) => items.iter().map(UserInput::text_bytes).sum(),
}
}
#[must_use]
pub fn text_chars(&self) -> usize {
match self {
Self::Text(text) => text.chars().count(),
Self::Content(items) => items.iter().map(UserInput::text_chars).sum(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
match self {
Self::Text(text) => text.trim().is_empty(),
Self::Content(items) => items.is_empty() || items.iter().all(UserInput::is_empty),
}
}
}
impl From<String> for PromptInput {
fn from(value: String) -> Self {
Self::Text(value)
}
}
impl From<&str> for PromptInput {
fn from(value: &str) -> Self {
Self::Text(value.to_owned())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum UserInput {
Text {
text: String,
},
Image {
image_url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
detail: Option<ImageDetail>,
},
LocalImage {
path: PathBuf,
#[serde(default, skip_serializing_if = "Option::is_none")]
detail: Option<ImageDetail>,
},
Audio {
audio_url: String,
},
LocalAudio {
path: PathBuf,
},
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ImageDetail {
Auto,
Low,
High,
Original,
}
impl UserInput {
#[must_use]
pub const fn text_bytes(&self) -> usize {
match self {
Self::Text { text } => text.len(),
Self::Image { .. }
| Self::LocalImage { .. }
| Self::Audio { .. }
| Self::LocalAudio { .. } => 0,
}
}
#[must_use]
pub fn text_chars(&self) -> usize {
match self {
Self::Text { text } => text.chars().count(),
Self::Image { .. }
| Self::LocalImage { .. }
| Self::Audio { .. }
| Self::LocalAudio { .. } => 0,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
match self {
Self::Text { text } => text.trim().is_empty(),
Self::Image { .. }
| Self::LocalImage { .. }
| Self::Audio { .. }
| Self::LocalAudio { .. } => false,
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum ReasoningMode {
#[default]
Standard,
Pro,
}
impl ReasoningMode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Standard => "standard",
Self::Pro => "pro",
}
}
pub(crate) const fn request_value(self) -> Option<&'static str> {
match self {
Self::Standard => None,
Self::Pro => Some("pro"),
}
}
}
impl fmt::Display for ReasoningMode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for ReasoningMode {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"standard" => Ok(Self::Standard),
"pro" => Ok(Self::Pro),
_ => Err(format!(
"invalid reasoning mode {value:?}; expected standard or pro"
)),
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum Thinking {
None,
Low,
Medium,
#[default]
High,
Xhigh,
Max,
}
impl Thinking {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::Xhigh => "xhigh",
Self::Max => "max",
}
}
}
impl fmt::Display for Thinking {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for Thinking {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"none" => Ok(Self::None),
"low" => Ok(Self::Low),
"medium" => Ok(Self::Medium),
"high" => Ok(Self::High),
"xhigh" => Ok(Self::Xhigh),
"max" => Ok(Self::Max),
_ => Err(format!(
"invalid reasoning effort {value:?}; expected none, low, medium, high, xhigh, or max"
)),
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{Prompt, ReasoningMode, Thinking};
#[test]
fn reasoning_configuration_parses_every_public_value() {
assert_eq!("standard".parse(), Ok(ReasoningMode::Standard));
assert_eq!("pro".parse(), Ok(ReasoningMode::Pro));
for (value, expected) in [
("none", Thinking::None),
("low", Thinking::Low),
("medium", Thinking::Medium),
("high", Thinking::High),
("xhigh", Thinking::Xhigh),
("max", Thinking::Max),
] {
assert_eq!(value.parse(), Ok(expected));
}
}
#[test]
fn prompt_serialization_contains_only_user_input() {
let prompt = Prompt::new("inspect the repository");
assert_eq!(
serde_json::to_value(prompt).unwrap(),
json!({ "instruction": "inspect the repository" })
);
}
#[test]
fn prompt_deserialization_rejects_session_policy() {
let error = serde_json::from_value::<Prompt>(json!({
"instruction": "inspect the repository",
"workspace": "/work/project"
}))
.unwrap_err();
assert!(error.to_string().contains("unknown field `workspace`"));
}
}