forge-provider-openai
OpenAI provider for the Forge SDK — implements the LanguageModel trait for OpenAI ChatCompletion API
OpenAI provider for the Forge SDK — implements the LanguageModel trait for OpenAI ChatCompletion API
Package contract
| Field | Value |
|---|---|
| Language | rust |
| Source version | 0.2.0 |
| Manifest | forge-rs/crates/forge-provider-openai/Cargo.toml |
| Source files | 7 |
| Evidence | Source reference; registry publication and runtime conformance are separate checks |
Import boundary
use forge_provider_openai;Use a source checkout or your verified private registry. Manifest coordinates identify the package; they do not establish that a public registry release exists.
Crate boundary
The following entries are taken from src/lib.rs. Feature conditions in the exact source still apply.
#[cfg(not(target_arch = "wasm32"))]
pub mod client;
#[cfg(not(target_arch = "wasm32"))]
pub mod config;
pub mod error;
#[cfg(not(target_arch = "wasm32"))]
pub mod model;
#[cfg(not(target_arch = "wasm32"))]
pub mod sse;
pub mod types;
#[cfg(not(target_arch = "wasm32"))]
pub use config::{OpenAiConfig, OpenAiConfigBuilder};
pub use error::OpenAiError;
#[cfg(not(target_arch = "wasm32"))]
pub use model::OpenAiLanguageModel;Source reference
Download package reference JSON. Each original source file and generated declaration artifact has its own SHA-256 digest. Function bodies and constant values are omitted from downloads. These are source declaration inventories, not compiler-resolved rustdoc, TypeDoc, DocC, or Dokka output. Private modules can contain public declarations that are not reachable through the package boundary; consult the entry point before importing.
config.rs
Read declaration text · 12 declaration entries
#[derive(Clone)]
pub struct OpenAiConfig {
}
pub fn builder(api_key: impl Into<String>) -> OpenAiConfigBuilder;
pub fn base_url(&self) -> &str;
pub fn organization(&self) -> Option<&str>;
pub fn timeout_seconds(&self) -> u64;
pub fn max_retries(&self) -> u32;
pub struct OpenAiConfigBuilder {
}
pub fn base_url(mut self, url: impl Into<String>) -> Self;
pub fn organization(mut self, org: impl Into<String>) -> Self;
pub fn timeout_seconds(mut self, seconds: u64) -> Self;
pub fn max_retries(mut self, retries: u32) -> Self;
pub fn build(self) -> OpenAiConfig;error.rs
Read declaration text · 1 declaration entries
#[derive(Debug, Error)]
pub enum OpenAiError {
/// The OpenAI API returned an HTTP error response.
///
/// Common status codes:
/// - 400: malformed request (check message format)
/// - 401: invalid API key
/// - 403: insufficient permissions
/// - 404: model not found
/// - 429: rate limited
/// - 500+: server error (retryable)
#[error(
"OpenAI API returned HTTP {status}: {body} (check request format and API key permissions)"
)]
HttpError {
/// The HTTP status code.
status: u16,
/// The response body (may contain OpenAI error details).
body: String,
},
/// Failed to connect to the OpenAI API endpoint.
///
/// Check network connectivity, firewall rules, and the configured base URL.
#[error("failed to connect to OpenAI API at '{url}': {reason} (check network connectivity and base_url configuration)")]
ConnectionFailed {
/// The URL that was being connected to.
url: String,
/// The underlying connection error.
reason: String,
},
/// The request timed out waiting for a response.
///
/// Consider increasing `timeout_seconds` in `OpenAiConfig` or reducing
/// `max_tokens` to speed up generation.
#[error("OpenAI API request to '{url}' timed out after {timeout_seconds}s (consider increasing timeout_seconds in OpenAiConfig or reducing max_tokens)")]
Timeout {
/// The URL that timed out.
url: String,
/// The configured timeout in seconds.
timeout_seconds: u64,
},
/// The API response could not be parsed.
///
/// This typically indicates an API version mismatch or an unexpected
/// response format. Check the OpenAI API changelog for breaking changes.
#[error("invalid response from OpenAI API: {reason} (this may indicate an API version mismatch; check the OpenAI API changelog)")]
InvalidResponse {
/// Description of what was wrong with the response.
reason: String,
},
/// The API returned a rate limit error (HTTP 429).
///
/// The provider will automatically retry with exponential backoff. If this
/// error surfaces, all retries have been exhausted.
#[error("OpenAI API rate limited; all retries exhausted{}", match .retry_after_ms {
Some(ms) => format!(" (server suggested retry after {ms}ms)"),
None => String::new(),
})]
RateLimited {
/// Milliseconds to wait before retrying, if provided by the server.
retry_after_ms: Option<u64>,
},
/// Authentication failed (HTTP 401).
///
/// The API key is invalid, expired, or missing.
#[error("OpenAI API authentication failed: {hint}")]
AuthenticationFailed {
/// Actionable hint for resolving the auth issue.
hint: String,
},
/// An error occurred while parsing the SSE stream.
///
/// This may indicate a network interruption during streaming or an
/// unexpected stream format.
#[error("OpenAI streaming error: {reason}")]
StreamError {
/// Description of the stream parsing failure.
reason: String,
},
/// JSON serialization or deserialization failed.
///
/// This typically means a request or response struct is malformed.
#[error("JSON serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
/// The request could not be constructed.
///
/// This indicates an internal error in request building.
#[error("failed to build HTTP request: {reason}")]
RequestBuildError {
/// Description of the request construction failure.
reason: String,
},
}lib.rs
Read declaration text · 9 declaration entries
#[cfg(not(target_arch = "wasm32"))]
pub mod client;
#[cfg(not(target_arch = "wasm32"))]
pub mod config;
pub mod error;
#[cfg(not(target_arch = "wasm32"))]
pub mod model;
#[cfg(not(target_arch = "wasm32"))]
pub mod sse;
pub mod types;
#[cfg(not(target_arch = "wasm32"))]
pub use config::{OpenAiConfig, OpenAiConfigBuilder};
pub use error::OpenAiError;
#[cfg(not(target_arch = "wasm32"))]
pub use model::OpenAiLanguageModel;model.rs
Read declaration text · 2 declaration entries
pub struct OpenAiLanguageModel {
}
pub fn new(model_id: String, config: OpenAiConfig) -> ForgeResult<Self>;sse.rs
Read declaration text · 8 declaration entries
pub fn parse_sse_response(body: &str) -> Result<Vec<ChatCompletionChunk>, OpenAiError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecoderState {
/// Stream is ongoing; more data can be fed.
Continue,
/// Terminal `[DONE]` sentinel observed; further bytes will be ignored.
Done,
}
pub struct OpenAiSseDecoder {
}
pub fn new() -> Self;
pub fn feed(&mut self, bytes: &[u8]) -> Result<DecoderState, OpenAiError>;
pub fn drain(&mut self) -> Vec<ChatCompletionChunk>;
pub fn is_done(&self) -> bool;
pub fn finish(&mut self) -> Result<Vec<ChatCompletionChunk>, OpenAiError>;types.rs
Read declaration text · 22 declaration entries
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatCompletionRequest {
/// The model ID (e.g., "gpt-4o").
pub model: String,
/// The conversation messages.
pub messages: Vec<ChatMessage>,
/// Tool definitions available to the model.
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ApiToolDefinition>>,
/// Sampling temperature (0.0 to 2.0).
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
/// Maximum tokens to generate.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
/// Top-p (nucleus) sampling threshold.
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f64>,
/// Up to 4 stop sequences.
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
/// Frequency penalty (-2.0 to 2.0).
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f64>,
/// Presence penalty (-2.0 to 2.0).
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f64>,
/// Seed for deterministic generation.
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
/// Response format for structured output.
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
/// Whether to stream the response.
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
/// Stream options (e.g., include usage in stream).
#[serde(skip_serializing_if = "Option::is_none")]
pub stream_options: Option<StreamOptions>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamOptions {
/// Whether to include usage statistics in the final stream chunk.
#[serde(skip_serializing_if = "Option::is_none")]
pub include_usage: Option<bool>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
/// The role: "system", "user", "assistant", or "tool".
pub role: String,
/// The message content (text or multi-part).
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<ChatContent>,
/// Tool calls made by the assistant.
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ApiToolCall>>,
/// The tool call ID this message responds to (for tool role).
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
/// The tool name (for tool role messages).
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ChatContent {
/// Plain text content.
Text(String),
/// Array of content parts (text and/or images).
Parts(Vec<ContentPart>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentPart {
/// A text content part.
Text {
/// The text content.
text: String,
},
/// An image URL content part.
ImageUrl {
/// The image URL object.
image_url: ImageUrlObject,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageUrlObject {
/// The image URL. For base64, use `data:<media_type>;base64,<data>`.
pub url: String
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiToolCall {
/// Unique identifier for this tool call.
pub id: String,
/// The type of tool call — always "function" for now.
#[serde(rename = "type")]
pub call_type: String,
/// The function being called.
pub function: FunctionCall
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionCall {
/// The function name.
pub name: String,
/// The function arguments as a JSON string.
pub arguments: String
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiToolDefinition {
/// The type — always "function".
#[serde(rename = "type")]
pub tool_type: String,
/// The function definition.
pub function: FunctionDef
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionDef {
/// The function name.
pub name: String,
/// A human-readable description of what the function does.
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// JSON Schema for the function's parameters.
#[serde(skip_serializing_if = "Option::is_none")]
pub parameters: Option<serde_json::Value>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseFormat {
/// The format type: "text", "json_object", or "json_schema".
#[serde(rename = "type")]
pub format_type: String,
/// JSON schema definition (only for `json_schema` type).
#[serde(skip_serializing_if = "Option::is_none")]
pub json_schema: Option<JsonSchemaFormat>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonSchemaFormat {
/// Name for the schema (required by OpenAI).
pub name: String,
/// The JSON Schema object.
pub schema: serde_json::Value,
/// Whether to enforce strict schema adherence.
#[serde(skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatCompletionResponse {
/// Unique response identifier.
pub id: String,
/// The list of completion choices.
pub choices: Vec<ChatCompletionChoice>,
/// Token usage statistics.
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<ApiUsage>,
/// The model that generated the response.
pub model: String
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatCompletionChoice {
/// The choice index.
pub index: u32,
/// The generated message.
pub message: ChatMessage,
/// Why generation stopped.
pub finish_reason: Option<String>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiUsage {
/// Tokens consumed by the prompt.
pub prompt_tokens: u64,
/// Tokens generated in the completion.
pub completion_tokens: u64,
/// Total tokens (prompt + completion).
pub total_tokens: u64
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatCompletionChunk {
/// Unique response identifier (same across all chunks).
pub id: String,
/// The list of chunk choices.
pub choices: Vec<ChunkChoice>,
/// Usage statistics (only present in the final chunk when `stream_options.include_usage` is true).
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<ApiUsage>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkChoice {
/// The choice index.
pub index: u32,
/// The content delta for this chunk.
pub delta: ChunkDelta,
/// Why generation stopped (present in the final chunk).
pub finish_reason: Option<String>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkDelta {
/// The role (typically only in the first chunk).
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
/// Text content delta.
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
/// Tool call deltas.
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ChunkToolCall>>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkToolCall {
/// The tool call index (for correlating deltas).
pub index: u32,
/// The tool call ID (may only be present in the first delta).
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
/// The tool call type (may only be present in the first delta).
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub call_type: Option<String>,
/// The function call delta.
#[serde(skip_serializing_if = "Option::is_none")]
pub function: Option<ChunkFunctionCall>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkFunctionCall {
/// The function name (may only be present in the first delta).
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// Partial function arguments (streamed incrementally).
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiErrorResponse {
/// The error detail object.
pub error: ApiErrorDetail
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiErrorDetail {
/// The error message.
pub message: String,
/// The error type (e.g., "invalid_request_error").
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub error_type: Option<String>,
/// The parameter that caused the error.
#[serde(skip_serializing_if = "Option::is_none")]
pub param: Option<String>,
/// The error code (e.g., "model_not_found").
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>
}