├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml └── workflows │ └── rust-check.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── assistant.rs ├── audio_speech.rs ├── audio_transcriptions.rs ├── audio_translations.rs ├── batch.rs ├── chat_completion.rs ├── completion.rs ├── data │ ├── batch_request.json │ ├── batch_result.json │ ├── problem.mp3 │ └── problem_cn.mp3 ├── embedding.rs ├── function_call.rs ├── function_call_role.rs ├── model.rs ├── openrouter.rs ├── realtime │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs └── vision.rs └── src ├── lib.rs ├── realtime ├── api.rs ├── client_event.rs ├── mod.rs ├── server_event.rs └── types.rs └── v1 ├── api.rs ├── assistant.rs ├── audio.rs ├── batch.rs ├── chat_completion.rs ├── common.rs ├── completion.rs ├── edit.rs ├── embedding.rs ├── error.rs ├── file.rs ├── fine_tuning.rs ├── image.rs ├── message.rs ├── mod.rs ├── model.rs ├── moderation.rs ├── run.rs ├── thread.rs └── types.rs /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | id: what-happened 11 | attributes: 12 | label: Describe the bug 13 | description: A clear and concise description of what the bug is, and any additional context. 14 | placeholder: Tell us what you see! 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: repro-steps 19 | attributes: 20 | label: To Reproduce 21 | description: Steps to reproduce the behavior. 22 | placeholder: | 23 | 1. Fetch a '...' 24 | 2. Update the '....' 25 | 3. See error 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: code-snippets 30 | attributes: 31 | label: Code snippets 32 | description: If applicable, add code snippets to help explain your problem. 33 | render: JavaScript 34 | validations: 35 | required: false 36 | - type: input 37 | id: os 38 | attributes: 39 | label: OS 40 | placeholder: macOS 41 | validations: 42 | required: true 43 | - type: input 44 | id: language-version 45 | attributes: 46 | label: Rust version 47 | placeholder: Rust v1.64.0 48 | validations: 49 | required: true 50 | - type: input 51 | id: lib-version 52 | attributes: 53 | label: Library version 54 | placeholder: openai-api-rs v0.1.0 55 | validations: 56 | required: true 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this library 3 | labels: ["feature-request"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this feature request! 9 | - type: textarea 10 | id: feature 11 | attributes: 12 | label: Describe the feature or improvement you're requesting 13 | description: A clear and concise description of what you want to happen. 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: context 18 | attributes: 19 | label: Additional context 20 | description: Add any other context about the feature request here. -------------------------------------------------------------------------------- /.github/workflows/rust-check.yml: -------------------------------------------------------------------------------- 1 | name: Rust Check 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Install rust 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: stable 21 | override: true 22 | components: rustfmt 23 | 24 | - name: Check formatting 25 | run: cargo fmt -- --check 26 | - name: Check clippy 27 | run: cargo clippy -- -D warnings 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openai-api-rs" 3 | version = "6.0.6" 4 | edition = "2021" 5 | authors = ["Dongri Jin "] 6 | license = "MIT" 7 | description = "OpenAI API client library for Rust (unofficial)" 8 | repository = "https://github.com/dongri/openai-api-rs" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [features] 13 | default = ["default-tls"] 14 | rustls = ["reqwest/rustls-tls", "tokio-tungstenite/rustls-tls-webpki-roots"] 15 | default-tls = ["reqwest/default-tls", "tokio-tungstenite/native-tls"] 16 | 17 | [dependencies.reqwest] 18 | version = "0.12" 19 | default-features = false 20 | features = ["charset", "http2", "json", "multipart", "socks"] 21 | 22 | [dependencies.tokio] 23 | version = "1" 24 | features = ["full"] 25 | 26 | [dependencies.serde] 27 | version = "1" 28 | features = ["derive"] 29 | 30 | [dependencies.serde_json] 31 | version = "1" 32 | 33 | [dependencies.bytes] 34 | version = "1.7.1" 35 | 36 | [dependencies.tokio-tungstenite] 37 | version = "0.24.0" 38 | features = ["connect"] 39 | 40 | [dependencies.futures-util] 41 | version = "0.3.31" 42 | features = ["sink", "std"] 43 | 44 | [dependencies.url] 45 | version = "2.5.4" 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dongri Jin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAI API client library for Rust (unofficial) 2 | The OpenAI API client Rust library provides convenient access to the OpenAI API from Rust applications. 3 | 4 | Check out the [docs.rs](https://docs.rs/openai-api-rs/). 5 | 6 | ## Installation: 7 | Cargo.toml 8 | ```toml 9 | [dependencies] 10 | openai-api-rs = "6.0.6" 11 | ``` 12 | 13 | ## Usage 14 | The library needs to be configured with your account's secret key, which is available on the [website](https://platform.openai.com/account/api-keys). We recommend setting it as an environment variable. Here's an example of initializing the library with the API key loaded from an environment variable and creating a completion: 15 | 16 | ### Set OPENAI_API_KEY or OPENROUTER_API_KEY to environment variable 17 | ```bash 18 | $ export OPENAI_API_KEY=sk-xxxxxxx 19 | or 20 | $ export OPENROUTER_API_KEY=sk-xxxxxxx 21 | ``` 22 | 23 | ### Create OpenAI client 24 | ```rust 25 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 26 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 27 | ``` 28 | 29 | ### Create OpenRouter client 30 | ```rust 31 | let api_key = env::var("OPENROUTER_API_KEY").unwrap().to_string(); 32 | let mut client = OpenAIClient::builder() 33 | .with_endpoint("https://openrouter.ai/api/v1") 34 | .with_api_key(api_key) 35 | .build()?; 36 | ``` 37 | 38 | ### Create request 39 | ```rust 40 | let req = ChatCompletionRequest::new( 41 | GPT4_O.to_string(), 42 | vec![chat_completion::ChatCompletionMessage { 43 | role: chat_completion::MessageRole::user, 44 | content: chat_completion::Content::Text(String::from("What is bitcoin?")), 45 | name: None, 46 | tool_calls: None, 47 | tool_call_id: None, 48 | }], 49 | ); 50 | ``` 51 | 52 | ### Send request 53 | ```rust 54 | let result = client.chat_completion(req)?; 55 | println!("Content: {:?}", result.choices[0].message.content); 56 | 57 | for (key, value) in client.headers.unwrap().iter() { 58 | println!("{}: {:?}", key, value); 59 | } 60 | ``` 61 | 62 | ### Set OPENAI_API_BASE to environment variable (optional) 63 | ```bash 64 | $ export OPENAI_API_BASE=https://api.openai.com/v1 65 | ``` 66 | 67 | ## Example of chat completion 68 | ```rust 69 | use openai_api_rs::v1::api::OpenAIClient; 70 | use openai_api_rs::v1::chat_completion::{self, ChatCompletionRequest}; 71 | use openai_api_rs::v1::common::GPT4_O; 72 | use std::env; 73 | 74 | #[tokio::main] 75 | async fn main() -> Result<(), Box> { 76 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 77 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 78 | 79 | let req = ChatCompletionRequest::new( 80 | GPT4_O.to_string(), 81 | vec![chat_completion::ChatCompletionMessage { 82 | role: chat_completion::MessageRole::user, 83 | content: chat_completion::Content::Text(String::from("What is bitcoin?")), 84 | name: None, 85 | tool_calls: None, 86 | tool_call_id: None, 87 | }], 88 | ); 89 | 90 | let result = client.chat_completion(req).await?; 91 | println!("Content: {:?}", result.choices[0].message.content); 92 | 93 | for (key, value) in client.headers.unwrap().iter() { 94 | println!("{}: {:?}", key, value); 95 | } 96 | 97 | Ok(()) 98 | } 99 | ``` 100 | 101 | ## Example for OpenRouter 102 | ```rust 103 | use openai_api_rs::v1::api::OpenAIClient; 104 | use openai_api_rs::v1::chat_completion::{self, ChatCompletionRequest}; 105 | use openai_api_rs::v1::common::GPT4_O_MINI; 106 | use std::env; 107 | 108 | #[tokio::main] 109 | async fn main() -> Result<(), Box> { 110 | let api_key = env::var("OPENROUTER_API_KEY").unwrap().to_string(); 111 | let mut client = OpenAIClient::builder() 112 | .with_endpoint("https://openrouter.ai/api/v1") 113 | .with_api_key(api_key) 114 | .build()?; 115 | 116 | let req = ChatCompletionRequest::new( 117 | GPT4_O_MINI.to_string(), 118 | vec![chat_completion::ChatCompletionMessage { 119 | role: chat_completion::MessageRole::user, 120 | content: chat_completion::Content::Text(String::from("What is bitcoin?")), 121 | name: None, 122 | tool_calls: None, 123 | tool_call_id: None, 124 | }], 125 | ); 126 | 127 | let result = client.chat_completion(req).await?; 128 | println!("Content: {:?}", result.choices[0].message.content); 129 | 130 | for (key, value) in client.headers.unwrap().iter() { 131 | println!("{}: {:?}", key, value); 132 | } 133 | 134 | Ok(()) 135 | } 136 | ``` 137 | 138 | More Examples: [examples](https://github.com/dongri/openai-api-rs/tree/main/examples) 139 | 140 | Check out the [full API documentation](https://platform.openai.com/docs/api-reference/completions) for examples of all the available functions. 141 | 142 | ## Supported APIs 143 | - [x] [Completions](https://platform.openai.com/docs/api-reference/completions) 144 | - [x] [Chat](https://platform.openai.com/docs/api-reference/chat) 145 | - [x] [Edits](https://platform.openai.com/docs/api-reference/edits) 146 | - [x] [Images](https://platform.openai.com/docs/api-reference/images) 147 | - [x] [Embeddings](https://platform.openai.com/docs/api-reference/embeddings) 148 | - [x] [Audio](https://platform.openai.com/docs/api-reference/audio) 149 | - [x] [Files](https://platform.openai.com/docs/api-reference/files) 150 | - [x] [Fine-tuning](https://platform.openai.com/docs/api-reference/fine-tuning) 151 | - [x] [Moderations](https://platform.openai.com/docs/api-reference/moderations) 152 | - [x] [Function calling](https://platform.openai.com/docs/guides/gpt/function-calling) 153 | - [x] [Assistants](https://platform.openai.com/docs/assistants/overview) 154 | - [x] [Batch](https://platform.openai.com/docs/api-reference/batch) 155 | - [x] [Realtime](https://platform.openai.com/docs/api-reference/realtime) 156 | 157 | ## License 158 | This project is licensed under [MIT license](https://github.com/dongri/openai-api-rs/blob/main/LICENSE). 159 | -------------------------------------------------------------------------------- /examples/assistant.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::assistant::AssistantRequest; 3 | use openai_api_rs::v1::common::GPT4_O; 4 | use openai_api_rs::v1::message::{CreateMessageRequest, MessageRole}; 5 | use openai_api_rs::v1::run::CreateRunRequest; 6 | use openai_api_rs::v1::thread::CreateThreadRequest; 7 | use std::collections::HashMap; 8 | use std::env; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<(), Box> { 12 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 13 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 14 | 15 | let mut tools = HashMap::new(); 16 | tools.insert("type".to_string(), "code_interpreter".to_string()); 17 | 18 | let req = AssistantRequest::new(GPT4_O.to_string()); 19 | let req = req 20 | .clone() 21 | .description("this is a test assistant".to_string()); 22 | let req = req.clone().instructions("You are a personal math tutor. When asked a question, write and run Python code to answer the question.".to_string()); 23 | let req = req.clone().tools(vec![tools]); 24 | println!("AssistantRequest: {:?}", req); 25 | 26 | let result = client.create_assistant(req).await?; 27 | println!("Create Assistant Result ID: {:?}", result.id); 28 | 29 | let thread_req = CreateThreadRequest::new(); 30 | let thread_result = client.create_thread(thread_req).await?; 31 | println!("Create Thread Result ID: {:?}", thread_result.id.clone()); 32 | 33 | let message_req = CreateMessageRequest::new( 34 | MessageRole::user, 35 | "`I need to solve the equation 3x + 11 = 14. Can you help me?".to_string(), 36 | ); 37 | 38 | let message_result = client 39 | .create_message(thread_result.id.clone(), message_req) 40 | .await?; 41 | println!("Create Message Result ID: {:?}", message_result.id.clone()); 42 | 43 | let run_req = CreateRunRequest::new(result.id); 44 | let run_result = client.create_run(thread_result.id.clone(), run_req).await?; 45 | println!("Create Run Result ID: {:?}", run_result.id.clone()); 46 | 47 | loop { 48 | let run_result = client 49 | .retrieve_run(thread_result.id.clone(), run_result.id.clone()) 50 | .await 51 | .unwrap(); 52 | if run_result.status == "completed" { 53 | break; 54 | } else { 55 | println!("waiting..."); 56 | std::thread::sleep(std::time::Duration::from_secs(1)); 57 | } 58 | } 59 | 60 | let list_message_result = client 61 | .list_messages(thread_result.id.clone()) 62 | .await 63 | .unwrap(); 64 | for data in list_message_result.data { 65 | for content in data.content { 66 | println!( 67 | "{:?}: {:?} {:?}", 68 | data.role, content.text.value, content.text.annotations 69 | ); 70 | } 71 | } 72 | 73 | Ok(()) 74 | } 75 | 76 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example assistant 77 | -------------------------------------------------------------------------------- /examples/audio_speech.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::audio::{self, AudioSpeechRequest, TTS_1}; 3 | use std::env; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Box> { 7 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 8 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 9 | 10 | let req = AudioSpeechRequest::new( 11 | TTS_1.to_string(), 12 | String::from("Money is not the problem, the problem is no money."), 13 | audio::VOICE_ALLOY.to_string(), 14 | String::from("examples/data/problem.mp3"), 15 | ); 16 | 17 | let result = client.audio_speech(req).await?; 18 | println!("{:?}", result); 19 | 20 | Ok(()) 21 | } 22 | 23 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example audio_speech 24 | -------------------------------------------------------------------------------- /examples/audio_transcriptions.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::audio::{AudioTranscriptionRequest, WHISPER_1}; 3 | use std::env; 4 | use std::fs::File; 5 | use std::io::Read; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), Box> { 9 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 10 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 11 | 12 | let file_path = "examples/data/problem.mp3"; 13 | 14 | // Test with file 15 | let req = AudioTranscriptionRequest::new(file_path.to_string(), WHISPER_1.to_string()); 16 | 17 | let req_json = req.clone().response_format("json".to_string()); 18 | 19 | let result = client.audio_transcription(req_json).await?; 20 | println!("{:?}", result); 21 | 22 | let req_raw = req.clone().response_format("text".to_string()); 23 | 24 | let result = client.audio_transcription_raw(req_raw).await?; 25 | println!("{:?}", result); 26 | 27 | // Test with bytes 28 | let mut file = File::open(file_path)?; 29 | let mut buffer = Vec::new(); 30 | file.read_to_end(&mut buffer)?; 31 | 32 | let req = AudioTranscriptionRequest::new_bytes(buffer, WHISPER_1.to_string()); 33 | 34 | let req_json = req.clone().response_format("json".to_string()); 35 | 36 | let result = client.audio_transcription(req_json).await?; 37 | println!("{:?}", result); 38 | 39 | Ok(()) 40 | } 41 | 42 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example audio_transcriptions 43 | -------------------------------------------------------------------------------- /examples/audio_translations.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::audio::{AudioTranslationRequest, WHISPER_1}; 3 | use std::env; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Box> { 7 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 8 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 9 | 10 | let req = AudioTranslationRequest::new( 11 | "examples/data/problem_cn.mp3".to_string(), 12 | WHISPER_1.to_string(), 13 | ); 14 | 15 | let result = client.audio_translation(req).await?; 16 | println!("{:?}", result); 17 | 18 | Ok(()) 19 | } 20 | 21 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example audio_transcriptions 22 | -------------------------------------------------------------------------------- /examples/batch.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::batch::CreateBatchRequest; 3 | use openai_api_rs::v1::file::FileUploadRequest; 4 | use serde_json::{from_str, to_string_pretty, Value}; 5 | use std::env; 6 | use std::fs::File; 7 | use std::io::Write; 8 | use std::str; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<(), Box> { 12 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 13 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 14 | 15 | let req = FileUploadRequest::new( 16 | "examples/data/batch_request.json".to_string(), 17 | "batch".to_string(), 18 | ); 19 | 20 | let result = client.upload_file(req).await?; 21 | println!("File id: {:?}", result.id); 22 | 23 | let input_file_id = result.id; 24 | let req = CreateBatchRequest::new( 25 | input_file_id.clone(), 26 | "/v1/chat/completions".to_string(), 27 | "24h".to_string(), 28 | ); 29 | 30 | let result = client.create_batch(req).await?; 31 | println!("Batch id: {:?}", result.id); 32 | 33 | let batch_id = result.id; 34 | let result = client.retrieve_batch(batch_id.to_string()).await?; 35 | println!("Batch status: {:?}", result.status); 36 | 37 | // sleep 30 seconds 38 | println!("Sleeping for 30 seconds..."); 39 | tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; 40 | 41 | let result = client.retrieve_batch(batch_id.to_string()).await?; 42 | 43 | let file_id = result.output_file_id.unwrap(); 44 | let result = client.retrieve_file_content(file_id).await?; 45 | let s = match str::from_utf8(&result) { 46 | Ok(v) => v.to_string(), 47 | Err(e) => panic!("Invalid UTF-8 sequence: {}", e), 48 | }; 49 | let json_value: Value = from_str(&s)?; 50 | let result_json = to_string_pretty(&json_value)?; 51 | 52 | let output_file_path = "examples/data/batch_result.json"; 53 | let mut file = File::create(output_file_path)?; 54 | file.write_all(result_json.as_bytes())?; 55 | 56 | println!("File writed to {:?}", output_file_path); 57 | 58 | Ok(()) 59 | } 60 | 61 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example batch 62 | -------------------------------------------------------------------------------- /examples/chat_completion.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::chat_completion::{self, ChatCompletionRequest}; 3 | use openai_api_rs::v1::common::GPT4_O_MINI; 4 | use std::env; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), Box> { 8 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 9 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 10 | 11 | let req = ChatCompletionRequest::new( 12 | GPT4_O_MINI.to_string(), 13 | vec![chat_completion::ChatCompletionMessage { 14 | role: chat_completion::MessageRole::user, 15 | content: chat_completion::Content::Text(String::from("What is bitcoin?")), 16 | name: None, 17 | tool_calls: None, 18 | tool_call_id: None, 19 | }], 20 | ); 21 | 22 | let result = client.chat_completion(req).await?; 23 | println!("Content: {:?}", result.choices[0].message.content); 24 | 25 | // print response headers 26 | for (key, value) in client.response_headers.unwrap().iter() { 27 | println!("{}: {:?}", key, value); 28 | } 29 | 30 | Ok(()) 31 | } 32 | 33 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example chat_completion 34 | -------------------------------------------------------------------------------- /examples/completion.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::completion::{self, CompletionRequest}; 3 | use std::env; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Box> { 7 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 8 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 9 | 10 | let req = CompletionRequest::new( 11 | completion::GPT3_TEXT_DAVINCI_003.to_string(), 12 | String::from("What is Bitcoin?"), 13 | ) 14 | .max_tokens(3000) 15 | .temperature(0.9) 16 | .top_p(1.0) 17 | .stop(vec![String::from(" Human:"), String::from(" AI:")]) 18 | .presence_penalty(0.6) 19 | .frequency_penalty(0.0); 20 | 21 | let result = client.completion(req).await?; 22 | println!("{:}", result.choices[0].text); 23 | 24 | Ok(()) 25 | } 26 | 27 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example completion 28 | -------------------------------------------------------------------------------- /examples/data/batch_request.json: -------------------------------------------------------------------------------- 1 | {"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-mini", "messages": [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is 2+2?"}]}} 2 | -------------------------------------------------------------------------------- /examples/data/batch_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom_id": "request-1", 3 | "error": null, 4 | "id": "batch_req_403hYy7nMxrxXFWXiwvoLG1q", 5 | "response": { 6 | "body": { 7 | "choices": [ 8 | { 9 | "finish_reason": "stop", 10 | "index": 0, 11 | "logprobs": null, 12 | "message": { 13 | "content": "2 + 2 equals 4.", 14 | "refusal": null, 15 | "role": "assistant" 16 | } 17 | } 18 | ], 19 | "created": 1724858089, 20 | "id": "chatcmpl-A1Efhv97EZNQeHKSLPnTmZex20gf2", 21 | "model": "gpt-4o-mini-2024-07-18", 22 | "object": "chat.completion", 23 | "system_fingerprint": "fp_f33667828e", 24 | "usage": { 25 | "completion_tokens": 8, 26 | "prompt_tokens": 24, 27 | "total_tokens": 32 28 | } 29 | }, 30 | "request_id": "af0bac0d82530234e09bd6b5d9fbf5cf", 31 | "status_code": 200 32 | } 33 | } -------------------------------------------------------------------------------- /examples/data/problem.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongri/openai-api-rs/27eba21f5a6f31ca5c0c1716045d8e2bacb53cce/examples/data/problem.mp3 -------------------------------------------------------------------------------- /examples/data/problem_cn.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongri/openai-api-rs/27eba21f5a6f31ca5c0c1716045d8e2bacb53cce/examples/data/problem_cn.mp3 -------------------------------------------------------------------------------- /examples/embedding.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::common::TEXT_EMBEDDING_3_SMALL; 3 | use openai_api_rs::v1::embedding::EmbeddingRequest; 4 | use std::env; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), Box> { 8 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 9 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 10 | 11 | let mut req = EmbeddingRequest::new( 12 | TEXT_EMBEDDING_3_SMALL.to_string(), 13 | vec!["story time".to_string(), "Once upon a time".to_string()], 14 | ); 15 | req.dimensions = Some(10); 16 | 17 | let result = client.embedding(req).await?; 18 | println!("{:?}", result.data); 19 | 20 | Ok(()) 21 | } 22 | 23 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example embedding 24 | -------------------------------------------------------------------------------- /examples/function_call.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::chat_completion::{self, ChatCompletionRequest}; 3 | use openai_api_rs::v1::common::GPT4_O; 4 | use openai_api_rs::v1::types; 5 | use serde::{Deserialize, Serialize}; 6 | use std::collections::HashMap; 7 | use std::{env, vec}; 8 | 9 | fn get_coin_price(coin: &str) -> f64 { 10 | let coin = coin.to_lowercase(); 11 | match coin.as_str() { 12 | "btc" | "bitcoin" => 10000.0, 13 | "eth" | "ethereum" => 1000.0, 14 | _ => 0.0, 15 | } 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> Result<(), Box> { 20 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 21 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 22 | 23 | let mut properties = HashMap::new(); 24 | properties.insert( 25 | "coin".to_string(), 26 | Box::new(types::JSONSchemaDefine { 27 | schema_type: Some(types::JSONSchemaType::String), 28 | description: Some("The cryptocurrency to get the price of".to_string()), 29 | ..Default::default() 30 | }), 31 | ); 32 | 33 | let req = ChatCompletionRequest::new( 34 | GPT4_O.to_string(), 35 | vec![chat_completion::ChatCompletionMessage { 36 | role: chat_completion::MessageRole::user, 37 | content: chat_completion::Content::Text(String::from("What is the price of Ethereum?")), 38 | name: None, 39 | tool_calls: None, 40 | tool_call_id: None, 41 | }], 42 | ) 43 | .tools(vec![chat_completion::Tool { 44 | r#type: chat_completion::ToolType::Function, 45 | function: types::Function { 46 | name: String::from("get_coin_price"), 47 | description: Some(String::from("Get the price of a cryptocurrency")), 48 | parameters: types::FunctionParameters { 49 | schema_type: types::JSONSchemaType::Object, 50 | properties: Some(properties), 51 | required: Some(vec![String::from("coin")]), 52 | }, 53 | }, 54 | }]) 55 | .tool_choice(chat_completion::ToolChoiceType::Auto); 56 | 57 | // debug request json 58 | // let serialized = serde_json::to_string(&req).unwrap(); 59 | // println!("{}", serialized); 60 | 61 | let result = client.chat_completion(req).await?; 62 | 63 | match result.choices[0].finish_reason { 64 | None => { 65 | println!("No finish_reason"); 66 | println!("{:?}", result.choices[0].message.content); 67 | } 68 | Some(chat_completion::FinishReason::stop) => { 69 | println!("Stop"); 70 | println!("{:?}", result.choices[0].message.content); 71 | } 72 | Some(chat_completion::FinishReason::length) => { 73 | println!("Length"); 74 | } 75 | Some(chat_completion::FinishReason::tool_calls) => { 76 | println!("ToolCalls"); 77 | #[derive(Deserialize, Serialize)] 78 | struct Currency { 79 | coin: String, 80 | } 81 | let tool_calls = result.choices[0].message.tool_calls.as_ref().unwrap(); 82 | for tool_call in tool_calls { 83 | let name = tool_call.function.name.clone().unwrap(); 84 | let arguments = tool_call.function.arguments.clone().unwrap(); 85 | let c: Currency = serde_json::from_str(&arguments)?; 86 | let coin = c.coin; 87 | if name == "get_coin_price" { 88 | let price = get_coin_price(&coin); 89 | println!("{} price: {}", coin, price); 90 | } 91 | } 92 | } 93 | Some(chat_completion::FinishReason::content_filter) => { 94 | println!("ContentFilter"); 95 | } 96 | Some(chat_completion::FinishReason::null) => { 97 | println!("Null"); 98 | } 99 | } 100 | Ok(()) 101 | } 102 | 103 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example function_call 104 | -------------------------------------------------------------------------------- /examples/function_call_role.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::chat_completion::{self, ChatCompletionRequest}; 3 | use openai_api_rs::v1::common::GPT4_O; 4 | use openai_api_rs::v1::types; 5 | use serde::{Deserialize, Serialize}; 6 | use std::collections::HashMap; 7 | use std::{env, vec}; 8 | 9 | fn get_coin_price(coin: &str) -> f64 { 10 | let coin = coin.to_lowercase(); 11 | match coin.as_str() { 12 | "btc" | "bitcoin" => 10000.0, 13 | "eth" | "ethereum" => 1000.0, 14 | _ => 0.0, 15 | } 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> Result<(), Box> { 20 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 21 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 22 | 23 | let mut properties = HashMap::new(); 24 | properties.insert( 25 | "coin".to_string(), 26 | Box::new(types::JSONSchemaDefine { 27 | schema_type: Some(types::JSONSchemaType::String), 28 | description: Some("The cryptocurrency to get the price of".to_string()), 29 | ..Default::default() 30 | }), 31 | ); 32 | 33 | let req = ChatCompletionRequest::new( 34 | GPT4_O.to_string(), 35 | vec![chat_completion::ChatCompletionMessage { 36 | role: chat_completion::MessageRole::user, 37 | content: chat_completion::Content::Text(String::from("What is the price of Ethereum?")), 38 | name: None, 39 | tool_calls: None, 40 | tool_call_id: None, 41 | }], 42 | ) 43 | .tools(vec![chat_completion::Tool { 44 | r#type: chat_completion::ToolType::Function, 45 | function: types::Function { 46 | name: String::from("get_coin_price"), 47 | description: Some(String::from("Get the price of a cryptocurrency")), 48 | parameters: types::FunctionParameters { 49 | schema_type: types::JSONSchemaType::Object, 50 | properties: Some(properties), 51 | required: Some(vec![String::from("coin")]), 52 | }, 53 | }, 54 | }]); 55 | 56 | let result = client.chat_completion(req).await?; 57 | 58 | match result.choices[0].finish_reason { 59 | None => { 60 | println!("No finish_reason"); 61 | println!("{:?}", result.choices[0].message.content); 62 | } 63 | Some(chat_completion::FinishReason::stop) => { 64 | println!("Stop"); 65 | println!("{:?}", result.choices[0].message.content); 66 | } 67 | Some(chat_completion::FinishReason::length) => { 68 | println!("Length"); 69 | } 70 | Some(chat_completion::FinishReason::tool_calls) => { 71 | println!("ToolCalls"); 72 | #[derive(Deserialize, Serialize)] 73 | struct Currency { 74 | coin: String, 75 | } 76 | let tool_calls = result.choices[0].message.tool_calls.as_ref().unwrap(); 77 | for tool_call in tool_calls { 78 | let function_call = &tool_call.function; 79 | let arguments = function_call.arguments.clone().unwrap(); 80 | let c: Currency = serde_json::from_str(&arguments)?; 81 | let coin = c.coin; 82 | println!("coin: {}", coin); 83 | let price = get_coin_price(&coin); 84 | println!("price: {}", price); 85 | 86 | let req = ChatCompletionRequest::new( 87 | GPT4_O.to_string(), 88 | vec![ 89 | chat_completion::ChatCompletionMessage { 90 | role: chat_completion::MessageRole::user, 91 | content: chat_completion::Content::Text(String::from( 92 | "What is the price of Ethereum?", 93 | )), 94 | name: None, 95 | tool_calls: None, 96 | tool_call_id: None, 97 | }, 98 | chat_completion::ChatCompletionMessage { 99 | role: chat_completion::MessageRole::function, 100 | content: chat_completion::Content::Text({ 101 | let price = get_coin_price(&coin); 102 | format!("{{\"price\": {}}}", price) 103 | }), 104 | name: Some(String::from("get_coin_price")), 105 | tool_calls: None, 106 | tool_call_id: None, 107 | }, 108 | ], 109 | ); 110 | 111 | let result = client.chat_completion(req).await?; 112 | println!("{:?}", result.choices[0].message.content); 113 | } 114 | } 115 | Some(chat_completion::FinishReason::content_filter) => { 116 | println!("ContentFilter"); 117 | } 118 | Some(chat_completion::FinishReason::null) => { 119 | println!("Null"); 120 | } 121 | } 122 | Ok(()) 123 | } 124 | 125 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example function_call_role 126 | -------------------------------------------------------------------------------- /examples/model.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use std::env; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<(), Box> { 6 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 7 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 8 | 9 | let result = client.list_models().await?; 10 | let models = result.data; 11 | 12 | for model in models { 13 | println!("Model id: {:?}", model.id); 14 | } 15 | 16 | let result = client.retrieve_model("gpt-4.1".to_string()).await?; 17 | println!("Model id: {:?}", result.id); 18 | println!("Model object: {:?}", result.object); 19 | 20 | Ok(()) 21 | } 22 | 23 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example model 24 | -------------------------------------------------------------------------------- /examples/openrouter.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::chat_completion::{self, ChatCompletionRequest}; 3 | use openai_api_rs::v1::common::GPT4_O_MINI; 4 | use std::env; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), Box> { 8 | let api_key = env::var("OPENROUTER_API_KEY").unwrap().to_string(); 9 | let mut client = OpenAIClient::builder() 10 | .with_endpoint("https://openrouter.ai/api/v1") 11 | .with_api_key(api_key) 12 | .build()?; 13 | 14 | let req = ChatCompletionRequest::new( 15 | GPT4_O_MINI.to_string(), 16 | vec![chat_completion::ChatCompletionMessage { 17 | role: chat_completion::MessageRole::user, 18 | content: chat_completion::Content::Text(String::from("What is bitcoin?")), 19 | name: None, 20 | tool_calls: None, 21 | tool_call_id: None, 22 | }], 23 | ); 24 | 25 | let result = client.chat_completion(req).await?; 26 | println!("Content: {:?}", result.choices[0].message.content); 27 | println!("Response Headers: {:?}", client.response_headers); 28 | 29 | Ok(()) 30 | } 31 | 32 | // OPENROUTER_API_KEY=xxxx cargo run --package openai-api-rs --example openrouter 33 | -------------------------------------------------------------------------------- /examples/realtime/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /examples/realtime/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "atomic-waker" 22 | version = "1.1.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.4.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 31 | 32 | [[package]] 33 | name = "backtrace" 34 | version = "0.3.74" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 37 | dependencies = [ 38 | "addr2line", 39 | "cfg-if", 40 | "libc", 41 | "miniz_oxide", 42 | "object", 43 | "rustc-demangle", 44 | "windows-targets", 45 | ] 46 | 47 | [[package]] 48 | name = "base64" 49 | version = "0.22.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 52 | 53 | [[package]] 54 | name = "bitflags" 55 | version = "2.6.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 58 | 59 | [[package]] 60 | name = "block-buffer" 61 | version = "0.10.4" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 64 | dependencies = [ 65 | "generic-array", 66 | ] 67 | 68 | [[package]] 69 | name = "bumpalo" 70 | version = "3.16.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 73 | 74 | [[package]] 75 | name = "byteorder" 76 | version = "1.5.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 79 | 80 | [[package]] 81 | name = "bytes" 82 | version = "1.7.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" 85 | 86 | [[package]] 87 | name = "cc" 88 | version = "1.1.28" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" 91 | dependencies = [ 92 | "shlex", 93 | ] 94 | 95 | [[package]] 96 | name = "cfg-if" 97 | version = "1.0.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 100 | 101 | [[package]] 102 | name = "core-foundation" 103 | version = "0.9.4" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 106 | dependencies = [ 107 | "core-foundation-sys", 108 | "libc", 109 | ] 110 | 111 | [[package]] 112 | name = "core-foundation-sys" 113 | version = "0.8.7" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 116 | 117 | [[package]] 118 | name = "cpufeatures" 119 | version = "0.2.14" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" 122 | dependencies = [ 123 | "libc", 124 | ] 125 | 126 | [[package]] 127 | name = "crypto-common" 128 | version = "0.1.6" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 131 | dependencies = [ 132 | "generic-array", 133 | "typenum", 134 | ] 135 | 136 | [[package]] 137 | name = "data-encoding" 138 | version = "2.6.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" 141 | 142 | [[package]] 143 | name = "digest" 144 | version = "0.10.7" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 147 | dependencies = [ 148 | "block-buffer", 149 | "crypto-common", 150 | ] 151 | 152 | [[package]] 153 | name = "either" 154 | version = "1.13.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 157 | 158 | [[package]] 159 | name = "encoding_rs" 160 | version = "0.8.34" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 163 | dependencies = [ 164 | "cfg-if", 165 | ] 166 | 167 | [[package]] 168 | name = "equivalent" 169 | version = "1.0.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 172 | 173 | [[package]] 174 | name = "errno" 175 | version = "0.3.9" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 178 | dependencies = [ 179 | "libc", 180 | "windows-sys 0.52.0", 181 | ] 182 | 183 | [[package]] 184 | name = "fastrand" 185 | version = "2.1.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 188 | 189 | [[package]] 190 | name = "fnv" 191 | version = "1.0.7" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 194 | 195 | [[package]] 196 | name = "foreign-types" 197 | version = "0.3.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 200 | dependencies = [ 201 | "foreign-types-shared", 202 | ] 203 | 204 | [[package]] 205 | name = "foreign-types-shared" 206 | version = "0.1.1" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 209 | 210 | [[package]] 211 | name = "form_urlencoded" 212 | version = "1.2.1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 215 | dependencies = [ 216 | "percent-encoding", 217 | ] 218 | 219 | [[package]] 220 | name = "futures-channel" 221 | version = "0.3.31" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 224 | dependencies = [ 225 | "futures-core", 226 | ] 227 | 228 | [[package]] 229 | name = "futures-core" 230 | version = "0.3.31" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 233 | 234 | [[package]] 235 | name = "futures-macro" 236 | version = "0.3.31" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 239 | dependencies = [ 240 | "proc-macro2", 241 | "quote", 242 | "syn", 243 | ] 244 | 245 | [[package]] 246 | name = "futures-sink" 247 | version = "0.3.31" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 250 | 251 | [[package]] 252 | name = "futures-task" 253 | version = "0.3.31" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 256 | 257 | [[package]] 258 | name = "futures-util" 259 | version = "0.3.31" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 262 | dependencies = [ 263 | "futures-core", 264 | "futures-macro", 265 | "futures-sink", 266 | "futures-task", 267 | "pin-project-lite", 268 | "pin-utils", 269 | "slab", 270 | ] 271 | 272 | [[package]] 273 | name = "generic-array" 274 | version = "0.14.7" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 277 | dependencies = [ 278 | "typenum", 279 | "version_check", 280 | ] 281 | 282 | [[package]] 283 | name = "getrandom" 284 | version = "0.2.15" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 287 | dependencies = [ 288 | "cfg-if", 289 | "libc", 290 | "wasi", 291 | ] 292 | 293 | [[package]] 294 | name = "gimli" 295 | version = "0.31.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 298 | 299 | [[package]] 300 | name = "h2" 301 | version = "0.4.6" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" 304 | dependencies = [ 305 | "atomic-waker", 306 | "bytes", 307 | "fnv", 308 | "futures-core", 309 | "futures-sink", 310 | "http", 311 | "indexmap", 312 | "slab", 313 | "tokio", 314 | "tokio-util", 315 | "tracing", 316 | ] 317 | 318 | [[package]] 319 | name = "hashbrown" 320 | version = "0.15.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 323 | 324 | [[package]] 325 | name = "hermit-abi" 326 | version = "0.3.9" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 329 | 330 | [[package]] 331 | name = "http" 332 | version = "1.1.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 335 | dependencies = [ 336 | "bytes", 337 | "fnv", 338 | "itoa", 339 | ] 340 | 341 | [[package]] 342 | name = "http-body" 343 | version = "1.0.1" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 346 | dependencies = [ 347 | "bytes", 348 | "http", 349 | ] 350 | 351 | [[package]] 352 | name = "http-body-util" 353 | version = "0.1.2" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 356 | dependencies = [ 357 | "bytes", 358 | "futures-util", 359 | "http", 360 | "http-body", 361 | "pin-project-lite", 362 | ] 363 | 364 | [[package]] 365 | name = "httparse" 366 | version = "1.9.5" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 369 | 370 | [[package]] 371 | name = "hyper" 372 | version = "1.4.1" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" 375 | dependencies = [ 376 | "bytes", 377 | "futures-channel", 378 | "futures-util", 379 | "h2", 380 | "http", 381 | "http-body", 382 | "httparse", 383 | "itoa", 384 | "pin-project-lite", 385 | "smallvec", 386 | "tokio", 387 | "want", 388 | ] 389 | 390 | [[package]] 391 | name = "hyper-rustls" 392 | version = "0.27.3" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" 395 | dependencies = [ 396 | "futures-util", 397 | "http", 398 | "hyper", 399 | "hyper-util", 400 | "rustls", 401 | "rustls-pki-types", 402 | "tokio", 403 | "tokio-rustls", 404 | "tower-service", 405 | ] 406 | 407 | [[package]] 408 | name = "hyper-tls" 409 | version = "0.6.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 412 | dependencies = [ 413 | "bytes", 414 | "http-body-util", 415 | "hyper", 416 | "hyper-util", 417 | "native-tls", 418 | "tokio", 419 | "tokio-native-tls", 420 | "tower-service", 421 | ] 422 | 423 | [[package]] 424 | name = "hyper-util" 425 | version = "0.1.9" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" 428 | dependencies = [ 429 | "bytes", 430 | "futures-channel", 431 | "futures-util", 432 | "http", 433 | "http-body", 434 | "hyper", 435 | "pin-project-lite", 436 | "socket2", 437 | "tokio", 438 | "tower-service", 439 | "tracing", 440 | ] 441 | 442 | [[package]] 443 | name = "idna" 444 | version = "0.5.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 447 | dependencies = [ 448 | "unicode-bidi", 449 | "unicode-normalization", 450 | ] 451 | 452 | [[package]] 453 | name = "indexmap" 454 | version = "2.6.0" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 457 | dependencies = [ 458 | "equivalent", 459 | "hashbrown", 460 | ] 461 | 462 | [[package]] 463 | name = "ipnet" 464 | version = "2.10.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 467 | 468 | [[package]] 469 | name = "itoa" 470 | version = "1.0.11" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 473 | 474 | [[package]] 475 | name = "js-sys" 476 | version = "0.3.70" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 479 | dependencies = [ 480 | "wasm-bindgen", 481 | ] 482 | 483 | [[package]] 484 | name = "libc" 485 | version = "0.2.159" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 488 | 489 | [[package]] 490 | name = "linux-raw-sys" 491 | version = "0.4.14" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 494 | 495 | [[package]] 496 | name = "lock_api" 497 | version = "0.4.12" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 500 | dependencies = [ 501 | "autocfg", 502 | "scopeguard", 503 | ] 504 | 505 | [[package]] 506 | name = "log" 507 | version = "0.4.22" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 510 | 511 | [[package]] 512 | name = "memchr" 513 | version = "2.7.4" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 516 | 517 | [[package]] 518 | name = "mime" 519 | version = "0.3.17" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 522 | 523 | [[package]] 524 | name = "mime_guess" 525 | version = "2.0.5" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 528 | dependencies = [ 529 | "mime", 530 | "unicase", 531 | ] 532 | 533 | [[package]] 534 | name = "miniz_oxide" 535 | version = "0.8.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 538 | dependencies = [ 539 | "adler2", 540 | ] 541 | 542 | [[package]] 543 | name = "mio" 544 | version = "1.0.2" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 547 | dependencies = [ 548 | "hermit-abi", 549 | "libc", 550 | "wasi", 551 | "windows-sys 0.52.0", 552 | ] 553 | 554 | [[package]] 555 | name = "native-tls" 556 | version = "0.2.12" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 559 | dependencies = [ 560 | "libc", 561 | "log", 562 | "openssl", 563 | "openssl-probe", 564 | "openssl-sys", 565 | "schannel", 566 | "security-framework", 567 | "security-framework-sys", 568 | "tempfile", 569 | ] 570 | 571 | [[package]] 572 | name = "object" 573 | version = "0.36.5" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 576 | dependencies = [ 577 | "memchr", 578 | ] 579 | 580 | [[package]] 581 | name = "once_cell" 582 | version = "1.20.2" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 585 | 586 | [[package]] 587 | name = "openai-api-rs" 588 | version = "5.0.10" 589 | dependencies = [ 590 | "bytes", 591 | "futures-util", 592 | "reqwest", 593 | "serde", 594 | "serde_json", 595 | "tokio", 596 | "tokio-tungstenite", 597 | ] 598 | 599 | [[package]] 600 | name = "openssl" 601 | version = "0.10.66" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" 604 | dependencies = [ 605 | "bitflags", 606 | "cfg-if", 607 | "foreign-types", 608 | "libc", 609 | "once_cell", 610 | "openssl-macros", 611 | "openssl-sys", 612 | ] 613 | 614 | [[package]] 615 | name = "openssl-macros" 616 | version = "0.1.1" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 619 | dependencies = [ 620 | "proc-macro2", 621 | "quote", 622 | "syn", 623 | ] 624 | 625 | [[package]] 626 | name = "openssl-probe" 627 | version = "0.1.5" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 630 | 631 | [[package]] 632 | name = "openssl-sys" 633 | version = "0.9.103" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" 636 | dependencies = [ 637 | "cc", 638 | "libc", 639 | "pkg-config", 640 | "vcpkg", 641 | ] 642 | 643 | [[package]] 644 | name = "parking_lot" 645 | version = "0.12.3" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 648 | dependencies = [ 649 | "lock_api", 650 | "parking_lot_core", 651 | ] 652 | 653 | [[package]] 654 | name = "parking_lot_core" 655 | version = "0.9.10" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 658 | dependencies = [ 659 | "cfg-if", 660 | "libc", 661 | "redox_syscall", 662 | "smallvec", 663 | "windows-targets", 664 | ] 665 | 666 | [[package]] 667 | name = "percent-encoding" 668 | version = "2.3.1" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 671 | 672 | [[package]] 673 | name = "pin-project-lite" 674 | version = "0.2.14" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 677 | 678 | [[package]] 679 | name = "pin-utils" 680 | version = "0.1.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 683 | 684 | [[package]] 685 | name = "pkg-config" 686 | version = "0.3.31" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 689 | 690 | [[package]] 691 | name = "ppv-lite86" 692 | version = "0.2.20" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 695 | dependencies = [ 696 | "zerocopy", 697 | ] 698 | 699 | [[package]] 700 | name = "proc-macro2" 701 | version = "1.0.87" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" 704 | dependencies = [ 705 | "unicode-ident", 706 | ] 707 | 708 | [[package]] 709 | name = "quote" 710 | version = "1.0.37" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 713 | dependencies = [ 714 | "proc-macro2", 715 | ] 716 | 717 | [[package]] 718 | name = "rand" 719 | version = "0.8.5" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 722 | dependencies = [ 723 | "libc", 724 | "rand_chacha", 725 | "rand_core", 726 | ] 727 | 728 | [[package]] 729 | name = "rand_chacha" 730 | version = "0.3.1" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 733 | dependencies = [ 734 | "ppv-lite86", 735 | "rand_core", 736 | ] 737 | 738 | [[package]] 739 | name = "rand_core" 740 | version = "0.6.4" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 743 | dependencies = [ 744 | "getrandom", 745 | ] 746 | 747 | [[package]] 748 | name = "realtime" 749 | version = "0.1.0" 750 | dependencies = [ 751 | "futures-channel", 752 | "futures-util", 753 | "openai-api-rs", 754 | "serde", 755 | "serde_json", 756 | "tokio", 757 | ] 758 | 759 | [[package]] 760 | name = "redox_syscall" 761 | version = "0.5.7" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 764 | dependencies = [ 765 | "bitflags", 766 | ] 767 | 768 | [[package]] 769 | name = "reqwest" 770 | version = "0.12.8" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" 773 | dependencies = [ 774 | "base64", 775 | "bytes", 776 | "encoding_rs", 777 | "futures-core", 778 | "futures-util", 779 | "h2", 780 | "http", 781 | "http-body", 782 | "http-body-util", 783 | "hyper", 784 | "hyper-rustls", 785 | "hyper-tls", 786 | "hyper-util", 787 | "ipnet", 788 | "js-sys", 789 | "log", 790 | "mime", 791 | "mime_guess", 792 | "native-tls", 793 | "once_cell", 794 | "percent-encoding", 795 | "pin-project-lite", 796 | "rustls-pemfile", 797 | "serde", 798 | "serde_json", 799 | "serde_urlencoded", 800 | "sync_wrapper", 801 | "tokio", 802 | "tokio-native-tls", 803 | "tokio-socks", 804 | "tower-service", 805 | "url", 806 | "wasm-bindgen", 807 | "wasm-bindgen-futures", 808 | "web-sys", 809 | "windows-registry", 810 | ] 811 | 812 | [[package]] 813 | name = "ring" 814 | version = "0.17.8" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 817 | dependencies = [ 818 | "cc", 819 | "cfg-if", 820 | "getrandom", 821 | "libc", 822 | "spin", 823 | "untrusted", 824 | "windows-sys 0.52.0", 825 | ] 826 | 827 | [[package]] 828 | name = "rustc-demangle" 829 | version = "0.1.24" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 832 | 833 | [[package]] 834 | name = "rustix" 835 | version = "0.38.37" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 838 | dependencies = [ 839 | "bitflags", 840 | "errno", 841 | "libc", 842 | "linux-raw-sys", 843 | "windows-sys 0.52.0", 844 | ] 845 | 846 | [[package]] 847 | name = "rustls" 848 | version = "0.23.14" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" 851 | dependencies = [ 852 | "once_cell", 853 | "rustls-pki-types", 854 | "rustls-webpki", 855 | "subtle", 856 | "zeroize", 857 | ] 858 | 859 | [[package]] 860 | name = "rustls-pemfile" 861 | version = "2.2.0" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 864 | dependencies = [ 865 | "rustls-pki-types", 866 | ] 867 | 868 | [[package]] 869 | name = "rustls-pki-types" 870 | version = "1.9.0" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" 873 | 874 | [[package]] 875 | name = "rustls-webpki" 876 | version = "0.102.8" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 879 | dependencies = [ 880 | "ring", 881 | "rustls-pki-types", 882 | "untrusted", 883 | ] 884 | 885 | [[package]] 886 | name = "ryu" 887 | version = "1.0.18" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 890 | 891 | [[package]] 892 | name = "schannel" 893 | version = "0.1.26" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" 896 | dependencies = [ 897 | "windows-sys 0.59.0", 898 | ] 899 | 900 | [[package]] 901 | name = "scopeguard" 902 | version = "1.2.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 905 | 906 | [[package]] 907 | name = "security-framework" 908 | version = "2.11.1" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 911 | dependencies = [ 912 | "bitflags", 913 | "core-foundation", 914 | "core-foundation-sys", 915 | "libc", 916 | "security-framework-sys", 917 | ] 918 | 919 | [[package]] 920 | name = "security-framework-sys" 921 | version = "2.12.0" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" 924 | dependencies = [ 925 | "core-foundation-sys", 926 | "libc", 927 | ] 928 | 929 | [[package]] 930 | name = "serde" 931 | version = "1.0.210" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 934 | dependencies = [ 935 | "serde_derive", 936 | ] 937 | 938 | [[package]] 939 | name = "serde_derive" 940 | version = "1.0.210" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 943 | dependencies = [ 944 | "proc-macro2", 945 | "quote", 946 | "syn", 947 | ] 948 | 949 | [[package]] 950 | name = "serde_json" 951 | version = "1.0.128" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 954 | dependencies = [ 955 | "itoa", 956 | "memchr", 957 | "ryu", 958 | "serde", 959 | ] 960 | 961 | [[package]] 962 | name = "serde_urlencoded" 963 | version = "0.7.1" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 966 | dependencies = [ 967 | "form_urlencoded", 968 | "itoa", 969 | "ryu", 970 | "serde", 971 | ] 972 | 973 | [[package]] 974 | name = "sha1" 975 | version = "0.10.6" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 978 | dependencies = [ 979 | "cfg-if", 980 | "cpufeatures", 981 | "digest", 982 | ] 983 | 984 | [[package]] 985 | name = "shlex" 986 | version = "1.3.0" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 989 | 990 | [[package]] 991 | name = "signal-hook-registry" 992 | version = "1.4.2" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 995 | dependencies = [ 996 | "libc", 997 | ] 998 | 999 | [[package]] 1000 | name = "slab" 1001 | version = "0.4.9" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1004 | dependencies = [ 1005 | "autocfg", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "smallvec" 1010 | version = "1.13.2" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1013 | 1014 | [[package]] 1015 | name = "socket2" 1016 | version = "0.5.7" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1019 | dependencies = [ 1020 | "libc", 1021 | "windows-sys 0.52.0", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "spin" 1026 | version = "0.9.8" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1029 | 1030 | [[package]] 1031 | name = "subtle" 1032 | version = "2.6.1" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1035 | 1036 | [[package]] 1037 | name = "syn" 1038 | version = "2.0.79" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 1041 | dependencies = [ 1042 | "proc-macro2", 1043 | "quote", 1044 | "unicode-ident", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "sync_wrapper" 1049 | version = "1.0.1" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 1052 | dependencies = [ 1053 | "futures-core", 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "tempfile" 1058 | version = "3.13.0" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 1061 | dependencies = [ 1062 | "cfg-if", 1063 | "fastrand", 1064 | "once_cell", 1065 | "rustix", 1066 | "windows-sys 0.59.0", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "thiserror" 1071 | version = "1.0.64" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 1074 | dependencies = [ 1075 | "thiserror-impl", 1076 | ] 1077 | 1078 | [[package]] 1079 | name = "thiserror-impl" 1080 | version = "1.0.64" 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" 1082 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 1083 | dependencies = [ 1084 | "proc-macro2", 1085 | "quote", 1086 | "syn", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "tinyvec" 1091 | version = "1.8.0" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1094 | dependencies = [ 1095 | "tinyvec_macros", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "tinyvec_macros" 1100 | version = "0.1.1" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1103 | 1104 | [[package]] 1105 | name = "tokio" 1106 | version = "1.40.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 1109 | dependencies = [ 1110 | "backtrace", 1111 | "bytes", 1112 | "libc", 1113 | "mio", 1114 | "parking_lot", 1115 | "pin-project-lite", 1116 | "signal-hook-registry", 1117 | "socket2", 1118 | "tokio-macros", 1119 | "windows-sys 0.52.0", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "tokio-macros" 1124 | version = "2.4.0" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1127 | dependencies = [ 1128 | "proc-macro2", 1129 | "quote", 1130 | "syn", 1131 | ] 1132 | 1133 | [[package]] 1134 | name = "tokio-native-tls" 1135 | version = "0.3.1" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1138 | dependencies = [ 1139 | "native-tls", 1140 | "tokio", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "tokio-rustls" 1145 | version = "0.26.0" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 1148 | dependencies = [ 1149 | "rustls", 1150 | "rustls-pki-types", 1151 | "tokio", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "tokio-socks" 1156 | version = "0.5.2" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" 1159 | dependencies = [ 1160 | "either", 1161 | "futures-util", 1162 | "thiserror", 1163 | "tokio", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "tokio-tungstenite" 1168 | version = "0.24.0" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" 1171 | dependencies = [ 1172 | "futures-util", 1173 | "log", 1174 | "native-tls", 1175 | "tokio", 1176 | "tokio-native-tls", 1177 | "tungstenite", 1178 | ] 1179 | 1180 | [[package]] 1181 | name = "tokio-util" 1182 | version = "0.7.12" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 1185 | dependencies = [ 1186 | "bytes", 1187 | "futures-core", 1188 | "futures-sink", 1189 | "pin-project-lite", 1190 | "tokio", 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "tower-service" 1195 | version = "0.3.3" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1198 | 1199 | [[package]] 1200 | name = "tracing" 1201 | version = "0.1.40" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1204 | dependencies = [ 1205 | "pin-project-lite", 1206 | "tracing-core", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "tracing-core" 1211 | version = "0.1.32" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1214 | dependencies = [ 1215 | "once_cell", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "try-lock" 1220 | version = "0.2.5" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1223 | 1224 | [[package]] 1225 | name = "tungstenite" 1226 | version = "0.24.0" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" 1229 | dependencies = [ 1230 | "byteorder", 1231 | "bytes", 1232 | "data-encoding", 1233 | "http", 1234 | "httparse", 1235 | "log", 1236 | "native-tls", 1237 | "rand", 1238 | "sha1", 1239 | "thiserror", 1240 | "utf-8", 1241 | ] 1242 | 1243 | [[package]] 1244 | name = "typenum" 1245 | version = "1.17.0" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1248 | 1249 | [[package]] 1250 | name = "unicase" 1251 | version = "2.7.0" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 1254 | dependencies = [ 1255 | "version_check", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "unicode-bidi" 1260 | version = "0.3.17" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" 1263 | 1264 | [[package]] 1265 | name = "unicode-ident" 1266 | version = "1.0.13" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1269 | 1270 | [[package]] 1271 | name = "unicode-normalization" 1272 | version = "0.1.24" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 1275 | dependencies = [ 1276 | "tinyvec", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "untrusted" 1281 | version = "0.9.0" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1284 | 1285 | [[package]] 1286 | name = "url" 1287 | version = "2.5.2" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1290 | dependencies = [ 1291 | "form_urlencoded", 1292 | "idna", 1293 | "percent-encoding", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "utf-8" 1298 | version = "0.7.6" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1301 | 1302 | [[package]] 1303 | name = "vcpkg" 1304 | version = "0.2.15" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1307 | 1308 | [[package]] 1309 | name = "version_check" 1310 | version = "0.9.5" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1313 | 1314 | [[package]] 1315 | name = "want" 1316 | version = "0.3.1" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1319 | dependencies = [ 1320 | "try-lock", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "wasi" 1325 | version = "0.11.0+wasi-snapshot-preview1" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1328 | 1329 | [[package]] 1330 | name = "wasm-bindgen" 1331 | version = "0.2.93" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 1334 | dependencies = [ 1335 | "cfg-if", 1336 | "once_cell", 1337 | "wasm-bindgen-macro", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "wasm-bindgen-backend" 1342 | version = "0.2.93" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 1345 | dependencies = [ 1346 | "bumpalo", 1347 | "log", 1348 | "once_cell", 1349 | "proc-macro2", 1350 | "quote", 1351 | "syn", 1352 | "wasm-bindgen-shared", 1353 | ] 1354 | 1355 | [[package]] 1356 | name = "wasm-bindgen-futures" 1357 | version = "0.4.43" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" 1360 | dependencies = [ 1361 | "cfg-if", 1362 | "js-sys", 1363 | "wasm-bindgen", 1364 | "web-sys", 1365 | ] 1366 | 1367 | [[package]] 1368 | name = "wasm-bindgen-macro" 1369 | version = "0.2.93" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 1372 | dependencies = [ 1373 | "quote", 1374 | "wasm-bindgen-macro-support", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "wasm-bindgen-macro-support" 1379 | version = "0.2.93" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 1382 | dependencies = [ 1383 | "proc-macro2", 1384 | "quote", 1385 | "syn", 1386 | "wasm-bindgen-backend", 1387 | "wasm-bindgen-shared", 1388 | ] 1389 | 1390 | [[package]] 1391 | name = "wasm-bindgen-shared" 1392 | version = "0.2.93" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 1395 | 1396 | [[package]] 1397 | name = "web-sys" 1398 | version = "0.3.70" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" 1401 | dependencies = [ 1402 | "js-sys", 1403 | "wasm-bindgen", 1404 | ] 1405 | 1406 | [[package]] 1407 | name = "windows-registry" 1408 | version = "0.2.0" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1411 | dependencies = [ 1412 | "windows-result", 1413 | "windows-strings", 1414 | "windows-targets", 1415 | ] 1416 | 1417 | [[package]] 1418 | name = "windows-result" 1419 | version = "0.2.0" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1422 | dependencies = [ 1423 | "windows-targets", 1424 | ] 1425 | 1426 | [[package]] 1427 | name = "windows-strings" 1428 | version = "0.1.0" 1429 | source = "registry+https://github.com/rust-lang/crates.io-index" 1430 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1431 | dependencies = [ 1432 | "windows-result", 1433 | "windows-targets", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "windows-sys" 1438 | version = "0.52.0" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1441 | dependencies = [ 1442 | "windows-targets", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "windows-sys" 1447 | version = "0.59.0" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1450 | dependencies = [ 1451 | "windows-targets", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "windows-targets" 1456 | version = "0.52.6" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1459 | dependencies = [ 1460 | "windows_aarch64_gnullvm", 1461 | "windows_aarch64_msvc", 1462 | "windows_i686_gnu", 1463 | "windows_i686_gnullvm", 1464 | "windows_i686_msvc", 1465 | "windows_x86_64_gnu", 1466 | "windows_x86_64_gnullvm", 1467 | "windows_x86_64_msvc", 1468 | ] 1469 | 1470 | [[package]] 1471 | name = "windows_aarch64_gnullvm" 1472 | version = "0.52.6" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1475 | 1476 | [[package]] 1477 | name = "windows_aarch64_msvc" 1478 | version = "0.52.6" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1481 | 1482 | [[package]] 1483 | name = "windows_i686_gnu" 1484 | version = "0.52.6" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1487 | 1488 | [[package]] 1489 | name = "windows_i686_gnullvm" 1490 | version = "0.52.6" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1493 | 1494 | [[package]] 1495 | name = "windows_i686_msvc" 1496 | version = "0.52.6" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1499 | 1500 | [[package]] 1501 | name = "windows_x86_64_gnu" 1502 | version = "0.52.6" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1505 | 1506 | [[package]] 1507 | name = "windows_x86_64_gnullvm" 1508 | version = "0.52.6" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1511 | 1512 | [[package]] 1513 | name = "windows_x86_64_msvc" 1514 | version = "0.52.6" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1517 | 1518 | [[package]] 1519 | name = "zerocopy" 1520 | version = "0.7.35" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1523 | dependencies = [ 1524 | "byteorder", 1525 | "zerocopy-derive", 1526 | ] 1527 | 1528 | [[package]] 1529 | name = "zerocopy-derive" 1530 | version = "0.7.35" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1533 | dependencies = [ 1534 | "proc-macro2", 1535 | "quote", 1536 | "syn", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "zeroize" 1541 | version = "1.8.1" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1544 | -------------------------------------------------------------------------------- /examples/realtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "realtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | openai-api-rs = { path = "../../../openai-api-rs" } 8 | serde = { version = "1.0.210", features = ["derive"] } 9 | serde_json = "1.0.128" 10 | tokio = { version = "1.40.0", features = ["full"] } 11 | tokio-tungstenite = { version = "0.24.0", features = ["connect", "native-tls"] } 12 | futures-util = { version = "0.3.31", features = ["sink", "std"] } 13 | futures-channel = "0.3.31" 14 | -------------------------------------------------------------------------------- /examples/realtime/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | use std::env; 3 | 4 | use futures_util::{future, pin_mut, StreamExt}; 5 | use openai_api_rs::realtime::api::RealtimeClient; 6 | use openai_api_rs::realtime::client_event::{ConversationItemCreate, ResponseCreate}; 7 | use openai_api_rs::realtime::server_event::ServerEvent; 8 | use openai_api_rs::realtime::types::Item; 9 | use tokio::io::AsyncReadExt; 10 | use tokio_tungstenite::tungstenite::protocol::Message; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<(), Box> { 14 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 15 | let model = "gpt-4o-realtime-preview-2024-10-01".to_string(); 16 | 17 | let (stdin_tx, stdin_rx) = futures_channel::mpsc::unbounded(); 18 | tokio::spawn(read_stdin(stdin_tx)); 19 | 20 | let realtime_client = RealtimeClient::new(api_key, model); 21 | 22 | let (write, read) = realtime_client.connect().await.unwrap(); 23 | println!("WebSocket handshake complete"); 24 | 25 | let stdin_to_ws = stdin_rx.map(Ok).forward(write); 26 | 27 | let ws_to_stdout = { 28 | read.for_each(|message| async { 29 | let message = message.unwrap(); 30 | match message { 31 | Message::Text(_) => { 32 | let data = message.clone().into_data(); 33 | let server_event: ServerEvent = serde_json::from_slice(&data).unwrap(); 34 | match server_event { 35 | ServerEvent::ResponseOutputItemDone(_event) => { 36 | eprintln!(); 37 | } 38 | ServerEvent::ResponseAudioTranscriptDelta(event) => { 39 | eprint!("{}", event.delta.trim()); 40 | } 41 | ServerEvent::Error(e) => { 42 | eprint!("{e:?}"); 43 | } 44 | _ => {} 45 | } 46 | } 47 | Message::Close(_) => { 48 | eprintln!("Close"); 49 | exit(0); 50 | } 51 | _ => {} 52 | } 53 | }) 54 | }; 55 | 56 | pin_mut!(stdin_to_ws, ws_to_stdout); 57 | future::select(stdin_to_ws, ws_to_stdout).await; 58 | 59 | Ok(()) 60 | } 61 | 62 | async fn read_stdin(tx: futures_channel::mpsc::UnboundedSender) { 63 | let mut stdin = tokio::io::stdin(); 64 | loop { 65 | let mut buf = vec![0; 2048]; 66 | let n = match stdin.read(&mut buf).await { 67 | Err(_) | Ok(0) => break, 68 | Ok(n) => n, 69 | }; 70 | buf.truncate(n); 71 | let text = String::from_utf8_lossy(&buf).into_owned(); 72 | let item = Item::try_from(serde_json::json!({ 73 | "type": "message", 74 | "role": "user", 75 | "content": [ 76 | { 77 | "type": "input_text", 78 | "text": text.trim() 79 | } 80 | ] 81 | })) 82 | .unwrap(); 83 | let event = ConversationItemCreate { 84 | item, 85 | ..Default::default() 86 | }; 87 | let message: Message = event.into(); 88 | tx.unbounded_send(message).unwrap(); 89 | tx.unbounded_send(ResponseCreate::default().into()).unwrap(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/vision.rs: -------------------------------------------------------------------------------- 1 | use openai_api_rs::v1::api::OpenAIClient; 2 | use openai_api_rs::v1::chat_completion::{self, ChatCompletionRequest}; 3 | use openai_api_rs::v1::common::GPT4_O; 4 | use std::env; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), Box> { 8 | let api_key = env::var("OPENAI_API_KEY").unwrap().to_string(); 9 | let mut client = OpenAIClient::builder().with_api_key(api_key).build()?; 10 | 11 | let req = ChatCompletionRequest::new( 12 | GPT4_O.to_string(), 13 | vec![chat_completion::ChatCompletionMessage { 14 | role: chat_completion::MessageRole::user, 15 | content: chat_completion::Content::ImageUrl(vec![ 16 | chat_completion::ImageUrl { 17 | r#type: chat_completion::ContentType::text, 18 | text: Some(String::from("What's in this image?")), 19 | image_url: None, 20 | }, 21 | chat_completion::ImageUrl { 22 | r#type: chat_completion::ContentType::image_url, 23 | text: None, 24 | image_url: Some(chat_completion::ImageUrlType { 25 | url: String::from( 26 | "https://upload.wikimedia.org/wikipedia/commons/5/50/Bitcoin.png", 27 | ), 28 | }), 29 | }, 30 | ]), 31 | name: None, 32 | tool_calls: None, 33 | tool_call_id: None, 34 | }], 35 | ); 36 | 37 | let result = client.chat_completion(req).await?; 38 | println!("{:?}", result.choices[0].message.content); 39 | 40 | Ok(()) 41 | } 42 | 43 | // OPENAI_API_KEY=xxxx cargo run --package openai-api-rs --example vision 44 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod realtime; 2 | pub mod v1; 3 | -------------------------------------------------------------------------------- /src/realtime/api.rs: -------------------------------------------------------------------------------- 1 | use futures_util::stream::{SplitSink, SplitStream}; 2 | use futures_util::StreamExt; 3 | use tokio::net::TcpStream; 4 | use tokio_tungstenite::{ 5 | connect_async, 6 | tungstenite::{client::IntoClientRequest, protocol::Message}, 7 | MaybeTlsStream, WebSocketStream, 8 | }; 9 | 10 | const WSS_URL: &str = "wss://api.openai.com/v1/realtime"; 11 | 12 | pub struct RealtimeClient { 13 | pub wss_url: String, 14 | pub api_key: String, 15 | pub model: String, 16 | } 17 | 18 | impl RealtimeClient { 19 | pub fn new(api_key: String, model: String) -> Self { 20 | let wss_url = std::env::var("WSS_URL").unwrap_or_else(|_| WSS_URL.to_owned()); 21 | Self::new_with_endpoint(wss_url, api_key, model) 22 | } 23 | 24 | pub fn new_with_endpoint(wss_url: String, api_key: String, model: String) -> Self { 25 | Self { 26 | wss_url, 27 | api_key, 28 | model, 29 | } 30 | } 31 | 32 | pub async fn connect( 33 | &self, 34 | ) -> Result< 35 | ( 36 | SplitSink>, Message>, 37 | SplitStream>>, 38 | ), 39 | Box, 40 | > { 41 | let url = format!("{}?model={}", self.wss_url, self.model); 42 | let mut request = url.into_client_request()?; 43 | let api_key = self.api_key.clone(); 44 | request 45 | .headers_mut() 46 | .insert("Authorization", format!("Bearer {api_key}").parse()?); 47 | request 48 | .headers_mut() 49 | .insert("OpenAI-Beta", "realtime=v1".parse()?); 50 | let (ws_stream, _) = connect_async(request).await?; 51 | let (write, read) = ws_stream.split(); 52 | Ok((write, read)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/realtime/client_event.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use tokio_tungstenite::tungstenite::Message; 3 | 4 | use crate::realtime::types::{Item, Session}; 5 | 6 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 7 | pub struct SessionUpdate { 8 | #[serde(skip_serializing_if = "Option::is_none")] 9 | pub event_id: Option, 10 | pub session: Session, 11 | } 12 | 13 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 14 | pub struct InputAudioBufferAppend { 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub event_id: Option, 17 | pub audio: String, 18 | } 19 | 20 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 21 | pub struct InputAudioBufferCommit { 22 | #[serde(skip_serializing_if = "Option::is_none")] 23 | pub event_id: Option, 24 | } 25 | 26 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 27 | pub struct InputAudioBufferClear { 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | pub event_id: Option, 30 | } 31 | 32 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 33 | pub struct ConversationItemCreate { 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub event_id: Option, 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | pub previous_item_id: Option, 38 | pub item: Item, 39 | } 40 | 41 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 42 | pub struct ConversationItemTruncate { 43 | #[serde(skip_serializing_if = "Option::is_none")] 44 | pub event_id: Option, 45 | pub item_id: String, 46 | pub content_index: u32, 47 | pub audio_end_ms: u32, 48 | } 49 | 50 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 51 | pub struct ConversationItemDelete { 52 | #[serde(skip_serializing_if = "Option::is_none")] 53 | pub event_id: Option, 54 | pub item_id: String, 55 | } 56 | 57 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 58 | pub struct ResponseCreate { 59 | #[serde(skip_serializing_if = "Option::is_none")] 60 | pub event_id: Option, 61 | pub response: Option, 62 | } 63 | 64 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 65 | pub struct ResponseCancel { 66 | #[serde(skip_serializing_if = "Option::is_none")] 67 | pub event_id: Option, 68 | } 69 | 70 | #[derive(Debug, Serialize, Deserialize)] 71 | #[serde(tag = "type")] 72 | pub enum ClientEvent { 73 | #[serde(rename = "session.update")] 74 | SessionUpdate(SessionUpdate), 75 | #[serde(rename = "input_audio_buffer.append")] 76 | InputAudioBufferAppend(InputAudioBufferAppend), 77 | #[serde(rename = "input_audio_buffer.commit")] 78 | InputAudioBufferCommit(InputAudioBufferCommit), 79 | #[serde(rename = "input_audio_buffer.clear")] 80 | InputAudioBufferClear(InputAudioBufferClear), 81 | #[serde(rename = "conversation.item.create")] 82 | ConversationItemCreate(ConversationItemCreate), 83 | #[serde(rename = "conversation.item.truncate")] 84 | ConversationItemTruncate(ConversationItemTruncate), 85 | #[serde(rename = "conversation.item.delete")] 86 | ConversationItemDelete(ConversationItemDelete), 87 | #[serde(rename = "response.create")] 88 | ResponseCreate(ResponseCreate), 89 | #[serde(rename = "response.cancel")] 90 | ResponseCancel(ResponseCancel), 91 | } 92 | 93 | impl From for Message { 94 | fn from(value: ClientEvent) -> Self { 95 | Message::Text(String::from(&value)) 96 | } 97 | } 98 | 99 | impl From<&ClientEvent> for String { 100 | fn from(value: &ClientEvent) -> Self { 101 | serde_json::to_string(value).unwrap() 102 | } 103 | } 104 | 105 | impl From for Message { 106 | fn from(value: ConversationItemCreate) -> Self { 107 | Self::from(ClientEvent::ConversationItemCreate(value)) 108 | } 109 | } 110 | 111 | impl From for Message { 112 | fn from(value: InputAudioBufferAppend) -> Self { 113 | Self::from(ClientEvent::InputAudioBufferAppend(value)) 114 | } 115 | } 116 | 117 | impl From for Message { 118 | fn from(value: InputAudioBufferCommit) -> Self { 119 | Self::from(ClientEvent::InputAudioBufferCommit(value)) 120 | } 121 | } 122 | 123 | impl From for Message { 124 | fn from(value: InputAudioBufferClear) -> Self { 125 | Self::from(ClientEvent::InputAudioBufferClear(value)) 126 | } 127 | } 128 | 129 | impl From for Message { 130 | fn from(value: SessionUpdate) -> Self { 131 | Self::from(ClientEvent::SessionUpdate(value)) 132 | } 133 | } 134 | 135 | impl From for Message { 136 | fn from(value: ConversationItemTruncate) -> Self { 137 | Self::from(ClientEvent::ConversationItemTruncate(value)) 138 | } 139 | } 140 | 141 | impl From for Message { 142 | fn from(value: ConversationItemDelete) -> Self { 143 | Self::from(ClientEvent::ConversationItemDelete(value)) 144 | } 145 | } 146 | 147 | impl From for Message { 148 | fn from(value: ResponseCreate) -> Self { 149 | Self::from(ClientEvent::ResponseCreate(value)) 150 | } 151 | } 152 | 153 | impl From for Message { 154 | fn from(value: ResponseCancel) -> Self { 155 | Self::from(ClientEvent::ResponseCancel(value)) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/realtime/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | pub mod client_event; 3 | pub mod server_event; 4 | pub mod types; 5 | -------------------------------------------------------------------------------- /src/realtime/server_event.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::realtime::types::{ 4 | APIError, ContentPart, Conversation, Item, RateLimit, Response, Session, 5 | }; 6 | 7 | #[derive(Debug, Serialize, Deserialize, Clone)] 8 | pub struct Error { 9 | pub event_id: String, 10 | pub error: APIError, 11 | } 12 | 13 | #[derive(Debug, Serialize, Deserialize, Clone)] 14 | pub struct SessionCreated { 15 | pub event_id: String, 16 | pub session: Session, 17 | } 18 | 19 | #[derive(Debug, Serialize, Deserialize, Clone)] 20 | pub struct SessionUpdated { 21 | pub event_id: String, 22 | pub session: Session, 23 | } 24 | 25 | #[derive(Debug, Serialize, Deserialize, Clone)] 26 | pub struct ConversationCreated { 27 | pub event_id: String, 28 | pub conversation: Conversation, 29 | } 30 | 31 | #[derive(Debug, Serialize, Deserialize, Clone)] 32 | pub struct InputAudioBufferCommited { 33 | pub event_id: String, 34 | pub previous_item_id: Option, 35 | pub item_id: String, 36 | } 37 | 38 | #[derive(Debug, Serialize, Deserialize, Clone)] 39 | pub struct InputAudioBufferCleared { 40 | pub event_id: String, 41 | } 42 | 43 | #[derive(Debug, Serialize, Deserialize, Clone)] 44 | pub struct InputAudioBufferSpeechStarted { 45 | pub event_id: String, 46 | pub audio_start_ms: u32, 47 | pub item_id: String, 48 | } 49 | 50 | #[derive(Debug, Serialize, Deserialize, Clone)] 51 | pub struct InputAudioBufferSpeechStopped { 52 | pub event_id: String, 53 | pub audio_end_ms: u32, 54 | pub item_id: String, 55 | } 56 | 57 | #[derive(Debug, Serialize, Deserialize, Clone)] 58 | pub struct ConversationItemCreated { 59 | pub event_id: String, 60 | pub previous_item_id: Option, 61 | pub item: Item, 62 | } 63 | 64 | #[derive(Debug, Serialize, Deserialize, Clone)] 65 | pub struct ConversationItemInputAudioTranscriptionCompleted { 66 | pub event_id: String, 67 | pub item_id: String, 68 | pub content_index: u32, 69 | pub transcript: String, 70 | } 71 | 72 | #[derive(Debug, Serialize, Deserialize, Clone)] 73 | pub struct ConversationItemInputAudioTranscriptionFailed { 74 | pub event_id: String, 75 | pub item_id: String, 76 | pub content_index: u32, 77 | pub error: APIError, 78 | } 79 | 80 | #[derive(Debug, Serialize, Deserialize, Clone)] 81 | pub struct ConversationItemTruncated { 82 | pub event_id: String, 83 | pub item_id: String, 84 | pub content_index: u32, 85 | pub audio_end_ms: u32, 86 | } 87 | 88 | #[derive(Debug, Serialize, Deserialize, Clone)] 89 | pub struct ConversationItemDeleted { 90 | pub event_id: String, 91 | pub item_id: String, 92 | } 93 | 94 | #[derive(Debug, Serialize, Deserialize, Clone)] 95 | pub struct OutputAudioBufferStarted { 96 | pub event_id: String, 97 | pub response_id: String, 98 | } 99 | 100 | #[derive(Debug, Serialize, Deserialize, Clone)] 101 | pub struct OutputAudioBufferStopped { 102 | pub event_id: String, 103 | pub response_id: String, 104 | } 105 | 106 | #[derive(Debug, Serialize, Deserialize, Clone)] 107 | pub struct OutputAudioBufferCleared { 108 | pub event_id: String, 109 | pub response_id: String, 110 | } 111 | 112 | #[derive(Debug, Serialize, Deserialize, Clone)] 113 | pub struct ResponseCreated { 114 | pub event_id: String, 115 | pub response: Response, 116 | } 117 | 118 | #[derive(Debug, Serialize, Deserialize, Clone)] 119 | pub struct ResponseDone { 120 | pub event_id: String, 121 | pub response: Response, 122 | } 123 | 124 | #[derive(Debug, Serialize, Deserialize, Clone)] 125 | pub struct ResponseOutputItemAdded { 126 | pub event_id: String, 127 | pub response_id: String, 128 | pub output_index: u32, 129 | pub item: Item, 130 | } 131 | 132 | #[derive(Debug, Serialize, Deserialize, Clone)] 133 | pub struct ResponseOutputItemDone { 134 | pub event_id: String, 135 | pub response_id: String, 136 | pub output_index: u32, 137 | pub item: Item, 138 | } 139 | 140 | #[derive(Debug, Serialize, Deserialize, Clone)] 141 | pub struct ResponseContentPartAdded { 142 | pub event_id: String, 143 | pub response_id: String, 144 | pub item_id: String, 145 | pub output_index: u32, 146 | pub content_index: u32, 147 | pub part: ContentPart, 148 | } 149 | 150 | #[derive(Debug, Serialize, Deserialize, Clone)] 151 | pub struct ResponseContentPartDone { 152 | pub event_id: String, 153 | pub response_id: String, 154 | pub item_id: String, 155 | pub output_index: u32, 156 | pub content_index: u32, 157 | pub part: ContentPart, 158 | } 159 | 160 | #[derive(Debug, Serialize, Deserialize, Clone)] 161 | pub struct ResponseTextDelta { 162 | pub event_id: String, 163 | pub response_id: String, 164 | pub item_id: String, 165 | pub output_index: u32, 166 | pub content_index: u32, 167 | pub delta: String, 168 | } 169 | 170 | #[derive(Debug, Serialize, Deserialize, Clone)] 171 | pub struct ResponseTextDone { 172 | pub event_id: String, 173 | pub response_id: String, 174 | pub item_id: String, 175 | pub output_index: u32, 176 | pub content_index: u32, 177 | pub text: String, 178 | } 179 | 180 | #[derive(Debug, Serialize, Deserialize, Clone)] 181 | pub struct ResponseAudioTranscriptDelta { 182 | pub event_id: String, 183 | pub response_id: String, 184 | pub item_id: String, 185 | pub output_index: u32, 186 | pub content_index: u32, 187 | pub delta: String, 188 | } 189 | 190 | #[derive(Debug, Serialize, Deserialize, Clone)] 191 | pub struct ResponseAudioTranscriptDone { 192 | pub event_id: String, 193 | pub response_id: String, 194 | pub item_id: String, 195 | pub output_index: u32, 196 | pub content_index: u32, 197 | pub transcript: String, 198 | } 199 | 200 | #[derive(Debug, Serialize, Deserialize, Clone)] 201 | pub struct ResponseAudioDelta { 202 | pub event_id: String, 203 | pub response_id: String, 204 | pub item_id: String, 205 | pub output_index: u32, 206 | pub content_index: u32, 207 | pub delta: String, 208 | } 209 | 210 | #[derive(Debug, Serialize, Deserialize, Clone)] 211 | pub struct ResponseAudioDone { 212 | pub event_id: String, 213 | pub response_id: String, 214 | pub item_id: String, 215 | pub output_index: u32, 216 | pub content_index: u32, 217 | } 218 | 219 | #[derive(Debug, Serialize, Deserialize, Clone)] 220 | pub struct ResponseFunctionCallArgumentsDelta { 221 | pub event_id: String, 222 | pub response_id: String, 223 | pub item_id: String, 224 | pub output_index: u32, 225 | pub call_id: String, 226 | pub delta: String, 227 | } 228 | 229 | #[derive(Debug, Serialize, Deserialize, Clone)] 230 | pub struct ResponseFunctionCallArgumentsDone { 231 | pub event_id: String, 232 | pub response_id: String, 233 | pub item_id: String, 234 | pub output_index: u32, 235 | pub call_id: String, 236 | pub arguments: String, 237 | } 238 | 239 | #[derive(Debug, Serialize, Deserialize, Clone)] 240 | pub struct RateLimitsUpdated { 241 | pub event_id: String, 242 | pub rate_limits: Vec, 243 | } 244 | 245 | #[derive(Debug, Serialize, Deserialize, Clone)] 246 | #[serde(tag = "type")] 247 | pub enum ServerEvent { 248 | #[serde(rename = "error")] 249 | Error(Error), 250 | #[serde(rename = "session.created")] 251 | SessionCreated(SessionCreated), 252 | #[serde(rename = "session.updated")] 253 | SessionUpdated(SessionUpdated), 254 | #[serde(rename = "conversation.created")] 255 | ConversationCreated(ConversationCreated), 256 | #[serde(rename = "input_audio_buffer.committed")] 257 | InputAudioBufferCommited(InputAudioBufferCommited), 258 | #[serde(rename = "input_audio_buffer.cleared")] 259 | InputAudioBufferCleared(InputAudioBufferCleared), 260 | #[serde(rename = "input_audio_buffer.speech_started")] 261 | InputAudioBufferSpeechStarted(InputAudioBufferSpeechStarted), 262 | #[serde(rename = "input_audio_buffer.speech_stopped")] 263 | InputAudioBufferSpeechStopped(InputAudioBufferSpeechStopped), 264 | #[serde(rename = "conversation.item.created")] 265 | ConversationItemCreated(ConversationItemCreated), 266 | #[serde(rename = "conversation.item.input_audio_transcription.completed")] 267 | ConversationItemInputAudioTranscriptionCompleted( 268 | ConversationItemInputAudioTranscriptionCompleted, 269 | ), 270 | #[serde(rename = "conversation.item.input_audio_transcription.failed")] 271 | ConversationItemInputAudioTranscriptionFailed(ConversationItemInputAudioTranscriptionFailed), 272 | #[serde(rename = "conversation.item.truncated")] 273 | ConversationItemTruncated(ConversationItemTruncated), 274 | #[serde(rename = "conversation.item.deleted")] 275 | ConversationItemDeleted(ConversationItemDeleted), 276 | #[serde(rename = "output_audio_buffer.started")] 277 | OutputAudioBufferStarted(OutputAudioBufferStarted), 278 | #[serde(rename = "output_audio_buffer.stopped")] 279 | OutputAudioBufferStopped(OutputAudioBufferStopped), 280 | #[serde(rename = "output_audio_buffer.cleared")] 281 | OutputAudioBufferCleared(OutputAudioBufferCleared), 282 | #[serde(rename = "response.created")] 283 | ResponseCreated(ResponseCreated), 284 | #[serde(rename = "response.done")] 285 | ResponseDone(ResponseDone), 286 | #[serde(rename = "response.output_item.added")] 287 | ResponseOutputItemAdded(ResponseOutputItemAdded), 288 | #[serde(rename = "response.output_item.done")] 289 | ResponseOutputItemDone(ResponseOutputItemDone), 290 | #[serde(rename = "response.content_part.added")] 291 | ResponseContentPartAdded(ResponseContentPartAdded), 292 | #[serde(rename = "response.content_part.done")] 293 | ResponseContentPartDone(ResponseContentPartDone), 294 | #[serde(rename = "response.text.delta")] 295 | ResponseTextDelta(ResponseTextDelta), 296 | #[serde(rename = "response.text.done")] 297 | ResponseTextDone(ResponseTextDone), 298 | #[serde(rename = "response.audio_transcript.delta")] 299 | ResponseAudioTranscriptDelta(ResponseAudioTranscriptDelta), 300 | #[serde(rename = "response.audio_transcript.done")] 301 | ResponseAudioTranscriptDone(ResponseAudioTranscriptDone), 302 | #[serde(rename = "response.audio.delta")] 303 | ResponseAudioDelta(ResponseAudioDelta), 304 | #[serde(rename = "response.audio.done")] 305 | ResponseAudioDone(ResponseAudioDone), 306 | #[serde(rename = "response.function_call_arguments.delta")] 307 | ResponseFunctionCallArgumentsDelta(ResponseFunctionCallArgumentsDelta), 308 | #[serde(rename = "response.function_call_arguments.done")] 309 | ResponseFunctionCallArgumentsDone(ResponseFunctionCallArgumentsDone), 310 | #[serde(rename = "rate_limits.updated")] 311 | RateLimitsUpdated(RateLimitsUpdated), 312 | } 313 | -------------------------------------------------------------------------------- /src/realtime/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 4 | pub struct Session { 5 | #[serde(skip_serializing_if = "Option::is_none")] 6 | pub modalities: Option>, 7 | #[serde(skip_serializing_if = "Option::is_none")] 8 | pub instructions: Option, 9 | #[serde(skip_serializing_if = "Option::is_none")] 10 | pub voice: Option, 11 | #[serde(skip_serializing_if = "Option::is_none")] 12 | pub input_audio_format: Option, 13 | #[serde(skip_serializing_if = "Option::is_none")] 14 | pub output_audio_format: Option, 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub input_audio_transcription: Option, 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | pub turn_detection: Option, 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub tools: Option>, 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub tool_choice: Option, 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub temperature: Option, 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | pub max_output_tokens: Option, 27 | } 28 | 29 | #[derive(Debug, Serialize, Deserialize, Clone)] 30 | #[serde(rename_all = "lowercase")] 31 | pub enum RealtimeVoice { 32 | Alloy, 33 | Ash, 34 | Ballad, 35 | Coral, 36 | Echo, 37 | Sage, 38 | Shimmer, 39 | Verse, 40 | } 41 | 42 | #[derive(Debug, Serialize, Deserialize, Clone)] 43 | pub enum AudioFormat { 44 | #[serde(rename = "pcm16")] 45 | PCM16, 46 | #[serde(rename = "g711_ulaw")] 47 | G711ULAW, 48 | #[serde(rename = "g711_alaw")] 49 | G711ALAW, 50 | } 51 | 52 | #[derive(Debug, Serialize, Deserialize, Clone)] 53 | pub struct AudioTranscription { 54 | pub language: Option, 55 | pub model: Option, 56 | pub prompt: Option, 57 | } 58 | 59 | #[derive(Debug, Serialize, Deserialize, Clone)] 60 | #[serde(tag = "type")] 61 | pub enum TurnDetection { 62 | #[serde(rename = "server_vad")] 63 | ServerVAD { 64 | threshold: f32, 65 | prefix_padding_ms: u32, 66 | silence_duration_ms: u32, 67 | }, 68 | } 69 | 70 | #[derive(Debug, Serialize, Deserialize, Clone)] 71 | #[serde(tag = "type")] 72 | pub enum ToolDefinition { 73 | #[serde(rename = "function")] 74 | Function { 75 | name: String, 76 | description: String, 77 | parameters: serde_json::Value, 78 | }, 79 | } 80 | 81 | #[derive(Debug, Serialize, Deserialize, Clone)] 82 | #[serde(rename_all = "lowercase")] 83 | pub enum ToolChoice { 84 | Auto, 85 | None, 86 | Required, 87 | #[serde(untagged)] 88 | Function { 89 | r#type: FunctionType, 90 | name: String, 91 | }, 92 | } 93 | 94 | #[derive(Debug, Serialize, Deserialize, Clone)] 95 | #[serde(rename_all = "lowercase")] 96 | pub enum FunctionType { 97 | Function, 98 | } 99 | 100 | #[derive(Debug, Serialize, Deserialize, Clone)] 101 | #[serde(untagged)] 102 | pub enum MaxOutputTokens { 103 | Num(u16), 104 | #[serde(rename = "inf")] 105 | Inf, 106 | } 107 | 108 | #[derive(Debug, Serialize, Deserialize, Clone)] 109 | #[serde(rename_all = "snake_case")] 110 | pub enum ItemType { 111 | Message, 112 | FunctionCall, 113 | FunctionCallOutput, 114 | } 115 | 116 | #[derive(Debug, Serialize, Deserialize, Clone)] 117 | #[serde(rename_all = "snake_case")] 118 | pub enum ItemStatus { 119 | Completed, 120 | InProgress, 121 | Incomplete, 122 | } 123 | 124 | #[derive(Debug, Serialize, Deserialize, Clone)] 125 | #[serde(rename_all = "lowercase")] 126 | pub enum ItemRole { 127 | User, 128 | Assistant, 129 | System, 130 | } 131 | 132 | #[derive(Debug, Serialize, Deserialize, Clone)] 133 | #[serde(rename_all = "snake_case")] 134 | pub enum ItemContentType { 135 | InputText, 136 | InputAudio, 137 | Text, 138 | Audio, 139 | } 140 | 141 | #[derive(Debug, Serialize, Deserialize, Clone)] 142 | pub struct ItemContent { 143 | pub r#type: ItemContentType, 144 | #[serde(skip_serializing_if = "Option::is_none")] 145 | pub text: Option, 146 | #[serde(skip_serializing_if = "Option::is_none")] 147 | pub audio: Option, 148 | #[serde(skip_serializing_if = "Option::is_none")] 149 | pub transcript: Option, 150 | } 151 | 152 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 153 | pub struct Item { 154 | #[serde(skip_serializing_if = "Option::is_none")] 155 | pub id: Option, 156 | #[serde(skip_serializing_if = "Option::is_none")] 157 | pub r#type: Option, 158 | #[serde(skip_serializing_if = "Option::is_none")] 159 | pub status: Option, 160 | #[serde(skip_serializing_if = "Option::is_none")] 161 | pub role: Option, 162 | #[serde(skip_serializing_if = "Option::is_none")] 163 | pub content: Option>, 164 | #[serde(skip_serializing_if = "Option::is_none")] 165 | pub call_id: Option, 166 | #[serde(skip_serializing_if = "Option::is_none")] 167 | pub name: Option, 168 | #[serde(skip_serializing_if = "Option::is_none")] 169 | pub arguments: Option, 170 | #[serde(skip_serializing_if = "Option::is_none")] 171 | pub output: Option, 172 | } 173 | 174 | impl TryFrom for Item { 175 | type Error = serde_json::Error; 176 | 177 | fn try_from(value: serde_json::Value) -> Result { 178 | serde_json::from_value(value) 179 | } 180 | } 181 | 182 | #[derive(Debug, Serialize, Deserialize, Clone)] 183 | pub struct APIError { 184 | pub r#type: String, 185 | pub code: Option, 186 | pub message: String, 187 | pub param: Option, 188 | pub event_id: Option, 189 | } 190 | 191 | #[derive(Debug, Serialize, Deserialize, Clone)] 192 | pub struct Conversation { 193 | pub id: String, 194 | pub object: String, 195 | } 196 | 197 | #[derive(Debug, Serialize, Deserialize, Clone)] 198 | pub struct Response { 199 | pub id: String, 200 | pub object: String, 201 | pub status: ResponseStatus, 202 | pub status_details: Option, 203 | pub output: Vec, 204 | pub usage: Option, 205 | } 206 | 207 | #[derive(Debug, Serialize, Deserialize, Clone)] 208 | pub struct Usage { 209 | pub total_tokens: u32, 210 | pub input_tokens: u32, 211 | pub output_tokens: u32, 212 | } 213 | 214 | #[derive(Debug, Serialize, Deserialize, Clone)] 215 | #[serde(rename_all = "snake_case")] 216 | pub enum ResponseStatus { 217 | InProgress, 218 | Completed, 219 | Cancelled, 220 | Failed, 221 | Incomplete, 222 | } 223 | 224 | #[derive(Debug, Serialize, Deserialize, Clone)] 225 | #[serde(tag = "type")] 226 | pub enum ResponseStatusDetail { 227 | #[serde(rename = "cancelled")] 228 | Cancelled { reason: CancelledReason }, 229 | #[serde(rename = "incomplete")] 230 | Incomplete { reason: IncompleteReason }, 231 | #[serde(rename = "failed")] 232 | Failed { error: Option }, 233 | } 234 | 235 | #[derive(Debug, Serialize, Deserialize, Clone)] 236 | pub struct FailedError { 237 | pub code: Option, 238 | pub message: Option, 239 | pub r#type: Option, 240 | } 241 | 242 | #[derive(Debug, Serialize, Deserialize, Clone)] 243 | #[serde(rename_all = "snake_case")] 244 | pub enum CancelledReason { 245 | TurnDetected, 246 | ClientCancelled, 247 | } 248 | 249 | #[derive(Debug, Serialize, Deserialize, Clone)] 250 | #[serde(rename_all = "snake_case")] 251 | pub enum IncompleteReason { 252 | Interruption, 253 | MaxOutputTokens, 254 | ContentFilter, 255 | } 256 | 257 | #[derive(Debug, Serialize, Deserialize, Clone)] 258 | #[serde(tag = "type")] 259 | pub enum ContentPart { 260 | #[serde(rename = "text")] 261 | Text { text: String }, 262 | #[serde(rename = "audio")] 263 | Audio { 264 | audio: Option, 265 | transcript: String, 266 | }, 267 | } 268 | 269 | #[derive(Debug, Serialize, Deserialize, Clone)] 270 | pub struct RateLimit { 271 | pub name: String, 272 | pub limit: u32, 273 | pub remaining: u32, 274 | pub reset_seconds: f32, 275 | } 276 | -------------------------------------------------------------------------------- /src/v1/api.rs: -------------------------------------------------------------------------------- 1 | use crate::v1::assistant::{ 2 | AssistantFileObject, AssistantFileRequest, AssistantObject, AssistantRequest, ListAssistant, 3 | ListAssistantFile, 4 | }; 5 | use crate::v1::audio::{ 6 | AudioSpeechRequest, AudioSpeechResponse, AudioTranscriptionRequest, AudioTranscriptionResponse, 7 | AudioTranslationRequest, AudioTranslationResponse, 8 | }; 9 | use crate::v1::batch::{BatchResponse, CreateBatchRequest, ListBatchResponse}; 10 | use crate::v1::chat_completion::{ChatCompletionRequest, ChatCompletionResponse}; 11 | use crate::v1::common; 12 | use crate::v1::completion::{CompletionRequest, CompletionResponse}; 13 | use crate::v1::edit::{EditRequest, EditResponse}; 14 | use crate::v1::embedding::{EmbeddingRequest, EmbeddingResponse}; 15 | use crate::v1::error::APIError; 16 | use crate::v1::file::{ 17 | FileDeleteRequest, FileDeleteResponse, FileListResponse, FileRetrieveResponse, 18 | FileUploadRequest, FileUploadResponse, 19 | }; 20 | use crate::v1::fine_tuning::{ 21 | CancelFineTuningJobRequest, CreateFineTuningJobRequest, FineTuningJobEvent, 22 | FineTuningJobObject, FineTuningPagination, ListFineTuningJobEventsRequest, 23 | RetrieveFineTuningJobRequest, 24 | }; 25 | use crate::v1::image::{ 26 | ImageEditRequest, ImageEditResponse, ImageGenerationRequest, ImageGenerationResponse, 27 | ImageVariationRequest, ImageVariationResponse, 28 | }; 29 | use crate::v1::message::{ 30 | CreateMessageRequest, ListMessage, ListMessageFile, MessageFileObject, MessageObject, 31 | ModifyMessageRequest, 32 | }; 33 | use crate::v1::model::{ModelResponse, ModelsResponse}; 34 | use crate::v1::moderation::{CreateModerationRequest, CreateModerationResponse}; 35 | use crate::v1::run::{ 36 | CreateRunRequest, CreateThreadAndRunRequest, ListRun, ListRunStep, ModifyRunRequest, RunObject, 37 | RunStepObject, 38 | }; 39 | use crate::v1::thread::{CreateThreadRequest, ModifyThreadRequest, ThreadObject}; 40 | 41 | use bytes::Bytes; 42 | use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; 43 | use reqwest::multipart::{Form, Part}; 44 | use reqwest::{Client, Method, Response}; 45 | use serde::Serialize; 46 | use serde_json::Value; 47 | use url::Url; 48 | 49 | use std::error::Error; 50 | use std::fs::{create_dir_all, File}; 51 | use std::io::Read; 52 | use std::io::Write; 53 | use std::path::Path; 54 | 55 | const API_URL_V1: &str = "https://api.openai.com/v1"; 56 | 57 | #[derive(Default)] 58 | pub struct OpenAIClientBuilder { 59 | api_endpoint: Option, 60 | api_key: Option, 61 | organization: Option, 62 | proxy: Option, 63 | timeout: Option, 64 | headers: Option, 65 | } 66 | 67 | #[derive(Debug)] 68 | pub struct OpenAIClient { 69 | api_endpoint: String, 70 | api_key: Option, 71 | organization: Option, 72 | proxy: Option, 73 | timeout: Option, 74 | headers: Option, 75 | pub response_headers: Option, 76 | } 77 | 78 | impl OpenAIClientBuilder { 79 | pub fn new() -> Self { 80 | Self::default() 81 | } 82 | 83 | pub fn with_api_key(mut self, api_key: impl Into) -> Self { 84 | self.api_key = Some(api_key.into()); 85 | self 86 | } 87 | 88 | pub fn with_endpoint(mut self, endpoint: impl Into) -> Self { 89 | self.api_endpoint = Some(endpoint.into()); 90 | self 91 | } 92 | 93 | pub fn with_organization(mut self, organization: impl Into) -> Self { 94 | self.organization = Some(organization.into()); 95 | self 96 | } 97 | 98 | pub fn with_proxy(mut self, proxy: impl Into) -> Self { 99 | self.proxy = Some(proxy.into()); 100 | self 101 | } 102 | 103 | pub fn with_timeout(mut self, timeout: u64) -> Self { 104 | self.timeout = Some(timeout); 105 | self 106 | } 107 | 108 | pub fn with_header(mut self, key: impl Into, value: impl Into) -> Self { 109 | let headers = self.headers.get_or_insert_with(HeaderMap::new); 110 | headers.insert( 111 | HeaderName::from_bytes(key.into().as_bytes()).expect("Invalid header name"), 112 | HeaderValue::from_str(&value.into()).expect("Invalid header value"), 113 | ); 114 | self 115 | } 116 | 117 | pub fn build(self) -> Result> { 118 | let api_endpoint = self.api_endpoint.unwrap_or_else(|| { 119 | std::env::var("OPENAI_API_BASE").unwrap_or_else(|_| API_URL_V1.to_owned()) 120 | }); 121 | 122 | Ok(OpenAIClient { 123 | api_endpoint, 124 | api_key: self.api_key, 125 | organization: self.organization, 126 | proxy: self.proxy, 127 | timeout: self.timeout, 128 | headers: self.headers, 129 | response_headers: None, 130 | }) 131 | } 132 | } 133 | 134 | impl OpenAIClient { 135 | pub fn builder() -> OpenAIClientBuilder { 136 | OpenAIClientBuilder::new() 137 | } 138 | 139 | async fn build_request(&self, method: Method, path: &str) -> reqwest::RequestBuilder { 140 | let url = self 141 | .build_url_with_preserved_query(path) 142 | .unwrap_or_else(|_| format!("{}/{}", self.api_endpoint, path)); 143 | 144 | let client = Client::builder(); 145 | 146 | #[cfg(feature = "rustls")] 147 | let client = client.use_rustls_tls(); 148 | 149 | let client = if let Some(timeout) = self.timeout { 150 | client.timeout(std::time::Duration::from_secs(timeout)) 151 | } else { 152 | client 153 | }; 154 | 155 | let client = if let Some(proxy) = &self.proxy { 156 | client.proxy(reqwest::Proxy::all(proxy).unwrap()) 157 | } else { 158 | client 159 | }; 160 | 161 | let client = client.build().unwrap(); 162 | 163 | let mut request = client.request(method, url); 164 | 165 | if let Some(api_key) = &self.api_key { 166 | request = request.header("Authorization", format!("Bearer {}", api_key)); 167 | } 168 | 169 | if let Some(organization) = &self.organization { 170 | request = request.header("openai-organization", organization); 171 | } 172 | 173 | if let Some(headers) = &self.headers { 174 | for (key, value) in headers { 175 | request = request.header(key, value); 176 | } 177 | } 178 | 179 | if Self::is_beta(path) { 180 | request = request.header("OpenAI-Beta", "assistants=v2"); 181 | } 182 | 183 | request 184 | } 185 | 186 | async fn post( 187 | &mut self, 188 | path: &str, 189 | body: &impl serde::ser::Serialize, 190 | ) -> Result { 191 | let request = self.build_request(Method::POST, path).await; 192 | let request = request.json(body); 193 | let response = request.send().await?; 194 | self.handle_response(response).await 195 | } 196 | 197 | async fn get(&mut self, path: &str) -> Result { 198 | let request = self.build_request(Method::GET, path).await; 199 | let response = request.send().await?; 200 | self.handle_response(response).await 201 | } 202 | 203 | async fn get_raw(&self, path: &str) -> Result { 204 | let request = self.build_request(Method::GET, path).await; 205 | let response = request.send().await?; 206 | Ok(response.bytes().await?) 207 | } 208 | 209 | async fn delete(&mut self, path: &str) -> Result { 210 | let request = self.build_request(Method::DELETE, path).await; 211 | let response = request.send().await?; 212 | self.handle_response(response).await 213 | } 214 | 215 | async fn post_form( 216 | &mut self, 217 | path: &str, 218 | form: Form, 219 | ) -> Result { 220 | let request = self.build_request(Method::POST, path).await; 221 | let request = request.multipart(form); 222 | let response = request.send().await?; 223 | self.handle_response(response).await 224 | } 225 | 226 | async fn post_form_raw(&self, path: &str, form: Form) -> Result { 227 | let request = self.build_request(Method::POST, path).await; 228 | let request = request.multipart(form); 229 | let response = request.send().await?; 230 | Ok(response.bytes().await?) 231 | } 232 | 233 | async fn handle_response( 234 | &mut self, 235 | response: Response, 236 | ) -> Result { 237 | let status = response.status(); 238 | let headers = response.headers().clone(); 239 | if status.is_success() { 240 | let text = response.text().await.unwrap_or_else(|_| "".to_string()); 241 | match serde_json::from_str::(&text) { 242 | Ok(parsed) => { 243 | self.response_headers = Some(headers); 244 | Ok(parsed) 245 | } 246 | Err(e) => Err(APIError::CustomError { 247 | message: format!("Failed to parse JSON: {} / response {}", e, text), 248 | }), 249 | } 250 | } else { 251 | let error_message = response 252 | .text() 253 | .await 254 | .unwrap_or_else(|_| "Unknown error".to_string()); 255 | Err(APIError::CustomError { 256 | message: format!("{}: {}", status, error_message), 257 | }) 258 | } 259 | } 260 | 261 | pub async fn completion( 262 | &mut self, 263 | req: CompletionRequest, 264 | ) -> Result { 265 | self.post("completions", &req).await 266 | } 267 | 268 | pub async fn edit(&mut self, req: EditRequest) -> Result { 269 | self.post("edits", &req).await 270 | } 271 | 272 | pub async fn image_generation( 273 | &mut self, 274 | req: ImageGenerationRequest, 275 | ) -> Result { 276 | self.post("images/generations", &req).await 277 | } 278 | 279 | pub async fn image_edit( 280 | &mut self, 281 | req: ImageEditRequest, 282 | ) -> Result { 283 | self.post("images/edits", &req).await 284 | } 285 | 286 | pub async fn image_variation( 287 | &mut self, 288 | req: ImageVariationRequest, 289 | ) -> Result { 290 | self.post("images/variations", &req).await 291 | } 292 | 293 | pub async fn embedding( 294 | &mut self, 295 | req: EmbeddingRequest, 296 | ) -> Result { 297 | self.post("embeddings", &req).await 298 | } 299 | 300 | pub async fn file_list(&mut self) -> Result { 301 | self.get("files").await 302 | } 303 | 304 | pub async fn upload_file( 305 | &mut self, 306 | req: FileUploadRequest, 307 | ) -> Result { 308 | let form = Self::create_form(&req, "file")?; 309 | self.post_form("files", form).await 310 | } 311 | 312 | pub async fn delete_file( 313 | &mut self, 314 | req: FileDeleteRequest, 315 | ) -> Result { 316 | self.delete(&format!("files/{}", req.file_id)).await 317 | } 318 | 319 | pub async fn retrieve_file( 320 | &mut self, 321 | file_id: String, 322 | ) -> Result { 323 | self.get(&format!("files/{}", file_id)).await 324 | } 325 | 326 | pub async fn retrieve_file_content(&self, file_id: String) -> Result { 327 | self.get_raw(&format!("files/{}/content", file_id)).await 328 | } 329 | 330 | pub async fn chat_completion( 331 | &mut self, 332 | req: ChatCompletionRequest, 333 | ) -> Result { 334 | self.post("chat/completions", &req).await 335 | } 336 | 337 | pub async fn audio_transcription( 338 | &mut self, 339 | req: AudioTranscriptionRequest, 340 | ) -> Result { 341 | // https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-response_format 342 | if let Some(response_format) = &req.response_format { 343 | if response_format != "json" && response_format != "verbose_json" { 344 | return Err(APIError::CustomError { 345 | message: "response_format must be either 'json' or 'verbose_json' please use audio_transcription_raw".to_string(), 346 | }); 347 | } 348 | } 349 | let form: Form; 350 | if req.clone().file.is_some() { 351 | form = Self::create_form(&req, "file")?; 352 | } else if let Some(bytes) = req.clone().bytes { 353 | form = Self::create_form_from_bytes(&req, bytes)?; 354 | } else { 355 | return Err(APIError::CustomError { 356 | message: "Either file or bytes must be provided".to_string(), 357 | }); 358 | } 359 | self.post_form("audio/transcriptions", form).await 360 | } 361 | 362 | pub async fn audio_transcription_raw( 363 | &mut self, 364 | req: AudioTranscriptionRequest, 365 | ) -> Result { 366 | // https://platform.openai.com/docs/api-reference/audio/createTranscription#audio-createtranscription-response_format 367 | if let Some(response_format) = &req.response_format { 368 | if response_format != "text" && response_format != "srt" && response_format != "vtt" { 369 | return Err(APIError::CustomError { 370 | message: "response_format must be either 'text', 'srt' or 'vtt', please use audio_transcription".to_string(), 371 | }); 372 | } 373 | } 374 | let form: Form; 375 | if req.clone().file.is_some() { 376 | form = Self::create_form(&req, "file")?; 377 | } else if let Some(bytes) = req.clone().bytes { 378 | form = Self::create_form_from_bytes(&req, bytes)?; 379 | } else { 380 | return Err(APIError::CustomError { 381 | message: "Either file or bytes must be provided".to_string(), 382 | }); 383 | } 384 | self.post_form_raw("audio/transcriptions", form).await 385 | } 386 | 387 | pub async fn audio_translation( 388 | &mut self, 389 | req: AudioTranslationRequest, 390 | ) -> Result { 391 | let form = Self::create_form(&req, "file")?; 392 | self.post_form("audio/translations", form).await 393 | } 394 | 395 | pub async fn audio_speech( 396 | &mut self, 397 | req: AudioSpeechRequest, 398 | ) -> Result { 399 | let request = self.build_request(Method::POST, "audio/speech").await; 400 | let request = request.json(&req); 401 | let response = request.send().await?; 402 | let headers = response.headers().clone(); 403 | let bytes = response.bytes().await?; 404 | let path = Path::new(req.output.as_str()); 405 | if let Some(parent) = path.parent() { 406 | match create_dir_all(parent) { 407 | Ok(_) => {} 408 | Err(e) => { 409 | return Err(APIError::CustomError { 410 | message: e.to_string(), 411 | }) 412 | } 413 | } 414 | } 415 | match File::create(path) { 416 | Ok(mut file) => match file.write_all(&bytes) { 417 | Ok(_) => {} 418 | Err(e) => { 419 | return Err(APIError::CustomError { 420 | message: e.to_string(), 421 | }) 422 | } 423 | }, 424 | Err(e) => { 425 | return Err(APIError::CustomError { 426 | message: e.to_string(), 427 | }) 428 | } 429 | } 430 | 431 | Ok(AudioSpeechResponse { 432 | result: true, 433 | headers: Some(headers), 434 | }) 435 | } 436 | 437 | pub async fn create_fine_tuning_job( 438 | &mut self, 439 | req: CreateFineTuningJobRequest, 440 | ) -> Result { 441 | self.post("fine_tuning/jobs", &req).await 442 | } 443 | 444 | pub async fn list_fine_tuning_jobs( 445 | &mut self, 446 | ) -> Result, APIError> { 447 | self.get("fine_tuning/jobs").await 448 | } 449 | 450 | pub async fn list_fine_tuning_job_events( 451 | &mut self, 452 | req: ListFineTuningJobEventsRequest, 453 | ) -> Result, APIError> { 454 | self.get(&format!( 455 | "fine_tuning/jobs/{}/events", 456 | req.fine_tuning_job_id 457 | )) 458 | .await 459 | } 460 | 461 | pub async fn retrieve_fine_tuning_job( 462 | &mut self, 463 | req: RetrieveFineTuningJobRequest, 464 | ) -> Result { 465 | self.get(&format!("fine_tuning/jobs/{}", req.fine_tuning_job_id)) 466 | .await 467 | } 468 | 469 | pub async fn cancel_fine_tuning_job( 470 | &mut self, 471 | req: CancelFineTuningJobRequest, 472 | ) -> Result { 473 | self.post( 474 | &format!("fine_tuning/jobs/{}/cancel", req.fine_tuning_job_id), 475 | &req, 476 | ) 477 | .await 478 | } 479 | 480 | pub async fn create_moderation( 481 | &mut self, 482 | req: CreateModerationRequest, 483 | ) -> Result { 484 | self.post("moderations", &req).await 485 | } 486 | 487 | pub async fn create_assistant( 488 | &mut self, 489 | req: AssistantRequest, 490 | ) -> Result { 491 | self.post("assistants", &req).await 492 | } 493 | 494 | pub async fn retrieve_assistant( 495 | &mut self, 496 | assistant_id: String, 497 | ) -> Result { 498 | self.get(&format!("assistants/{}", assistant_id)).await 499 | } 500 | 501 | pub async fn modify_assistant( 502 | &mut self, 503 | assistant_id: String, 504 | req: AssistantRequest, 505 | ) -> Result { 506 | self.post(&format!("assistants/{}", assistant_id), &req) 507 | .await 508 | } 509 | 510 | pub async fn delete_assistant( 511 | &mut self, 512 | assistant_id: String, 513 | ) -> Result { 514 | self.delete(&format!("assistants/{}", assistant_id)).await 515 | } 516 | 517 | pub async fn list_assistant( 518 | &mut self, 519 | limit: Option, 520 | order: Option, 521 | after: Option, 522 | before: Option, 523 | ) -> Result { 524 | let url = Self::query_params(limit, order, after, before, "assistants".to_string()); 525 | self.get(&url).await 526 | } 527 | 528 | pub async fn create_assistant_file( 529 | &mut self, 530 | assistant_id: String, 531 | req: AssistantFileRequest, 532 | ) -> Result { 533 | self.post(&format!("assistants/{}/files", assistant_id), &req) 534 | .await 535 | } 536 | 537 | pub async fn retrieve_assistant_file( 538 | &mut self, 539 | assistant_id: String, 540 | file_id: String, 541 | ) -> Result { 542 | self.get(&format!("assistants/{}/files/{}", assistant_id, file_id)) 543 | .await 544 | } 545 | 546 | pub async fn delete_assistant_file( 547 | &mut self, 548 | assistant_id: String, 549 | file_id: String, 550 | ) -> Result { 551 | self.delete(&format!("assistants/{}/files/{}", assistant_id, file_id)) 552 | .await 553 | } 554 | 555 | pub async fn list_assistant_file( 556 | &mut self, 557 | assistant_id: String, 558 | limit: Option, 559 | order: Option, 560 | after: Option, 561 | before: Option, 562 | ) -> Result { 563 | let url = Self::query_params( 564 | limit, 565 | order, 566 | after, 567 | before, 568 | format!("assistants/{}/files", assistant_id), 569 | ); 570 | self.get(&url).await 571 | } 572 | 573 | pub async fn create_thread( 574 | &mut self, 575 | req: CreateThreadRequest, 576 | ) -> Result { 577 | self.post("threads", &req).await 578 | } 579 | 580 | pub async fn retrieve_thread(&mut self, thread_id: String) -> Result { 581 | self.get(&format!("threads/{}", thread_id)).await 582 | } 583 | 584 | pub async fn modify_thread( 585 | &mut self, 586 | thread_id: String, 587 | req: ModifyThreadRequest, 588 | ) -> Result { 589 | self.post(&format!("threads/{}", thread_id), &req).await 590 | } 591 | 592 | pub async fn delete_thread( 593 | &mut self, 594 | thread_id: String, 595 | ) -> Result { 596 | self.delete(&format!("threads/{}", thread_id)).await 597 | } 598 | 599 | pub async fn create_message( 600 | &mut self, 601 | thread_id: String, 602 | req: CreateMessageRequest, 603 | ) -> Result { 604 | self.post(&format!("threads/{}/messages", thread_id), &req) 605 | .await 606 | } 607 | 608 | pub async fn retrieve_message( 609 | &mut self, 610 | thread_id: String, 611 | message_id: String, 612 | ) -> Result { 613 | self.get(&format!("threads/{}/messages/{}", thread_id, message_id)) 614 | .await 615 | } 616 | 617 | pub async fn modify_message( 618 | &mut self, 619 | thread_id: String, 620 | message_id: String, 621 | req: ModifyMessageRequest, 622 | ) -> Result { 623 | self.post( 624 | &format!("threads/{}/messages/{}", thread_id, message_id), 625 | &req, 626 | ) 627 | .await 628 | } 629 | 630 | pub async fn list_messages(&mut self, thread_id: String) -> Result { 631 | self.get(&format!("threads/{}/messages", thread_id)).await 632 | } 633 | 634 | pub async fn retrieve_message_file( 635 | &mut self, 636 | thread_id: String, 637 | message_id: String, 638 | file_id: String, 639 | ) -> Result { 640 | self.get(&format!( 641 | "threads/{}/messages/{}/files/{}", 642 | thread_id, message_id, file_id 643 | )) 644 | .await 645 | } 646 | 647 | pub async fn list_message_file( 648 | &mut self, 649 | thread_id: String, 650 | message_id: String, 651 | limit: Option, 652 | order: Option, 653 | after: Option, 654 | before: Option, 655 | ) -> Result { 656 | let url = Self::query_params( 657 | limit, 658 | order, 659 | after, 660 | before, 661 | format!("threads/{}/messages/{}/files", thread_id, message_id), 662 | ); 663 | self.get(&url).await 664 | } 665 | 666 | pub async fn create_run( 667 | &mut self, 668 | thread_id: String, 669 | req: CreateRunRequest, 670 | ) -> Result { 671 | self.post(&format!("threads/{}/runs", thread_id), &req) 672 | .await 673 | } 674 | 675 | pub async fn retrieve_run( 676 | &mut self, 677 | thread_id: String, 678 | run_id: String, 679 | ) -> Result { 680 | self.get(&format!("threads/{}/runs/{}", thread_id, run_id)) 681 | .await 682 | } 683 | 684 | pub async fn modify_run( 685 | &mut self, 686 | thread_id: String, 687 | run_id: String, 688 | req: ModifyRunRequest, 689 | ) -> Result { 690 | self.post(&format!("threads/{}/runs/{}", thread_id, run_id), &req) 691 | .await 692 | } 693 | 694 | pub async fn list_run( 695 | &mut self, 696 | thread_id: String, 697 | limit: Option, 698 | order: Option, 699 | after: Option, 700 | before: Option, 701 | ) -> Result { 702 | let url = Self::query_params( 703 | limit, 704 | order, 705 | after, 706 | before, 707 | format!("threads/{}/runs", thread_id), 708 | ); 709 | self.get(&url).await 710 | } 711 | 712 | pub async fn cancel_run( 713 | &mut self, 714 | thread_id: String, 715 | run_id: String, 716 | ) -> Result { 717 | self.post( 718 | &format!("threads/{}/runs/{}/cancel", thread_id, run_id), 719 | &ModifyRunRequest::default(), 720 | ) 721 | .await 722 | } 723 | 724 | pub async fn create_thread_and_run( 725 | &mut self, 726 | req: CreateThreadAndRunRequest, 727 | ) -> Result { 728 | self.post("threads/runs", &req).await 729 | } 730 | 731 | pub async fn retrieve_run_step( 732 | &mut self, 733 | thread_id: String, 734 | run_id: String, 735 | step_id: String, 736 | ) -> Result { 737 | self.get(&format!( 738 | "threads/{}/runs/{}/steps/{}", 739 | thread_id, run_id, step_id 740 | )) 741 | .await 742 | } 743 | 744 | pub async fn list_run_step( 745 | &mut self, 746 | thread_id: String, 747 | run_id: String, 748 | limit: Option, 749 | order: Option, 750 | after: Option, 751 | before: Option, 752 | ) -> Result { 753 | let url = Self::query_params( 754 | limit, 755 | order, 756 | after, 757 | before, 758 | format!("threads/{}/runs/{}/steps", thread_id, run_id), 759 | ); 760 | self.get(&url).await 761 | } 762 | 763 | pub async fn create_batch( 764 | &mut self, 765 | req: CreateBatchRequest, 766 | ) -> Result { 767 | self.post("batches", &req).await 768 | } 769 | 770 | pub async fn retrieve_batch(&mut self, batch_id: String) -> Result { 771 | self.get(&format!("batches/{}", batch_id)).await 772 | } 773 | 774 | pub async fn cancel_batch(&mut self, batch_id: String) -> Result { 775 | self.post( 776 | &format!("batches/{}/cancel", batch_id), 777 | &common::EmptyRequestBody {}, 778 | ) 779 | .await 780 | } 781 | 782 | pub async fn list_batch( 783 | &mut self, 784 | after: Option, 785 | limit: Option, 786 | ) -> Result { 787 | let url = Self::query_params(limit, None, after, None, "batches".to_string()); 788 | self.get(&url).await 789 | } 790 | 791 | pub async fn list_models(&mut self) -> Result { 792 | self.get("models").await 793 | } 794 | 795 | pub async fn retrieve_model(&mut self, model_id: String) -> Result { 796 | self.get(&format!("models/{}", model_id)).await 797 | } 798 | 799 | pub async fn delete_model( 800 | &mut self, 801 | model_id: String, 802 | ) -> Result { 803 | self.delete(&format!("models/{}", model_id)).await 804 | } 805 | 806 | fn build_url_with_preserved_query(&self, path: &str) -> Result { 807 | let (base, query_opt) = match self.api_endpoint.split_once('?') { 808 | Some((b, q)) => (b.trim_end_matches('/'), Some(q)), 809 | None => (self.api_endpoint.trim_end_matches('/'), None), 810 | }; 811 | 812 | let full_path = format!("{}/{}", base, path.trim_start_matches('/')); 813 | let mut url = Url::parse(&full_path)?; 814 | 815 | if let Some(query) = query_opt { 816 | for (k, v) in url::form_urlencoded::parse(query.as_bytes()) { 817 | url.query_pairs_mut().append_pair(&k, &v); 818 | } 819 | } 820 | Ok(url.to_string()) 821 | } 822 | 823 | fn query_params( 824 | limit: Option, 825 | order: Option, 826 | after: Option, 827 | before: Option, 828 | mut url: String, 829 | ) -> String { 830 | let mut params = vec![]; 831 | if let Some(limit) = limit { 832 | params.push(format!("limit={}", limit)); 833 | } 834 | if let Some(order) = order { 835 | params.push(format!("order={}", order)); 836 | } 837 | if let Some(after) = after { 838 | params.push(format!("after={}", after)); 839 | } 840 | if let Some(before) = before { 841 | params.push(format!("before={}", before)); 842 | } 843 | if !params.is_empty() { 844 | url = format!("{}?{}", url, params.join("&")); 845 | } 846 | url 847 | } 848 | 849 | fn is_beta(path: &str) -> bool { 850 | path.starts_with("assistants") || path.starts_with("threads") 851 | } 852 | 853 | fn create_form(req: &T, file_field: &str) -> Result 854 | where 855 | T: Serialize, 856 | { 857 | let json = match serde_json::to_value(req) { 858 | Ok(json) => json, 859 | Err(e) => { 860 | return Err(APIError::CustomError { 861 | message: e.to_string(), 862 | }) 863 | } 864 | }; 865 | let file_path = if let Value::Object(map) = &json { 866 | map.get(file_field) 867 | .and_then(|v| v.as_str()) 868 | .ok_or(APIError::CustomError { 869 | message: format!("Field '{}' not found or not a string", file_field), 870 | })? 871 | } else { 872 | return Err(APIError::CustomError { 873 | message: "Request is not a JSON object".to_string(), 874 | }); 875 | }; 876 | 877 | let mut file = match File::open(file_path) { 878 | Ok(file) => file, 879 | Err(e) => { 880 | return Err(APIError::CustomError { 881 | message: e.to_string(), 882 | }) 883 | } 884 | }; 885 | let mut buffer = Vec::new(); 886 | match file.read_to_end(&mut buffer) { 887 | Ok(_) => {} 888 | Err(e) => { 889 | return Err(APIError::CustomError { 890 | message: e.to_string(), 891 | }) 892 | } 893 | } 894 | 895 | let mut form = 896 | Form::new().part("file", Part::bytes(buffer).file_name(file_path.to_string())); 897 | 898 | if let Value::Object(map) = json { 899 | for (key, value) in map.into_iter() { 900 | if key != file_field { 901 | match value { 902 | Value::String(s) => { 903 | form = form.text(key, s); 904 | } 905 | Value::Number(n) => { 906 | form = form.text(key, n.to_string()); 907 | } 908 | _ => {} 909 | } 910 | } 911 | } 912 | } 913 | 914 | Ok(form) 915 | } 916 | 917 | fn create_form_from_bytes(req: &T, bytes: Vec) -> Result 918 | where 919 | T: Serialize, 920 | { 921 | let json = match serde_json::to_value(req) { 922 | Ok(json) => json, 923 | Err(e) => { 924 | return Err(APIError::CustomError { 925 | message: e.to_string(), 926 | }) 927 | } 928 | }; 929 | 930 | let mut form = Form::new().part("file", Part::bytes(bytes.clone()).file_name("file.mp3")); 931 | 932 | if let Value::Object(map) = json { 933 | for (key, value) in map.into_iter() { 934 | match value { 935 | Value::String(s) => { 936 | form = form.text(key, s); 937 | } 938 | Value::Number(n) => { 939 | form = form.text(key, n.to_string()); 940 | } 941 | _ => {} 942 | } 943 | } 944 | } 945 | 946 | Ok(form) 947 | } 948 | } 949 | -------------------------------------------------------------------------------- /src/v1/assistant.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | use super::types; 5 | use crate::impl_builder_methods; 6 | 7 | #[derive(Debug, Serialize, Clone)] 8 | pub struct AssistantRequest { 9 | pub model: String, 10 | #[serde(skip_serializing_if = "Option::is_none")] 11 | pub name: Option, 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub description: Option, 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub instructions: Option, 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub tools: Option>>, 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub tool_resources: Option, 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub metadata: Option>, 22 | } 23 | 24 | impl AssistantRequest { 25 | pub fn new(model: String) -> Self { 26 | Self { 27 | model, 28 | name: None, 29 | description: None, 30 | instructions: None, 31 | tools: None, 32 | tool_resources: None, 33 | metadata: None, 34 | } 35 | } 36 | } 37 | 38 | impl_builder_methods!( 39 | AssistantRequest, 40 | name: String, 41 | description: String, 42 | instructions: String, 43 | tools: Vec>, 44 | tool_resources: ToolResource, 45 | metadata: HashMap 46 | ); 47 | 48 | #[derive(Debug, Deserialize, Serialize)] 49 | pub struct AssistantObject { 50 | pub id: String, 51 | pub object: String, 52 | pub created_at: i64, 53 | #[serde(skip_serializing_if = "Option::is_none")] 54 | pub name: Option, 55 | #[serde(skip_serializing_if = "Option::is_none")] 56 | pub description: Option, 57 | pub model: String, 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub instructions: Option, 60 | pub tools: Vec, 61 | #[serde(skip_serializing_if = "Option::is_none")] 62 | pub tool_resources: Option, 63 | pub metadata: Option>, 64 | } 65 | 66 | #[derive(Debug, Deserialize, Serialize, Clone)] 67 | pub struct ToolResource { 68 | #[serde(skip_serializing_if = "Option::is_none")] 69 | pub code_interpreter: Option, 70 | #[serde(skip_serializing_if = "Option::is_none")] 71 | pub file_search: Option, 72 | } 73 | 74 | #[derive(Debug, Deserialize, Serialize, Clone)] 75 | pub struct CodeInterpreter { 76 | #[serde(skip_serializing_if = "Option::is_none")] 77 | pub file_ids: Option>, 78 | } 79 | 80 | #[derive(Debug, Deserialize, Serialize, Clone)] 81 | pub struct FileSearch { 82 | #[serde(skip_serializing_if = "Option::is_none")] 83 | pub vector_store_ids: Option>, 84 | #[serde(skip_serializing_if = "Option::is_none")] 85 | pub vector_stores: Option, 86 | } 87 | 88 | #[derive(Debug, Deserialize, Serialize, Clone)] 89 | pub struct VectorStores { 90 | #[serde(skip_serializing_if = "Option::is_none")] 91 | pub file_ids: Option>, 92 | #[serde(skip_serializing_if = "Option::is_none")] 93 | pub chunking_strategy: Option, 94 | #[serde(skip_serializing_if = "Option::is_none")] 95 | pub metadata: Option>, 96 | } 97 | 98 | #[derive(Debug, Deserialize, Serialize)] 99 | pub struct ListAssistant { 100 | pub object: String, 101 | pub data: Vec, 102 | } 103 | 104 | #[derive(Debug, Serialize, Clone)] 105 | pub struct AssistantFileRequest { 106 | pub file_id: String, 107 | } 108 | 109 | #[derive(Debug, Deserialize, Serialize)] 110 | pub struct AssistantFileObject { 111 | pub id: String, 112 | pub object: String, 113 | pub created_at: i64, 114 | pub assistant_id: String, 115 | } 116 | 117 | #[derive(Debug, Deserialize, Serialize)] 118 | pub struct ListAssistantFile { 119 | pub object: String, 120 | pub data: Vec, 121 | } 122 | -------------------------------------------------------------------------------- /src/v1/audio.rs: -------------------------------------------------------------------------------- 1 | use reqwest::header::HeaderMap; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::impl_builder_methods; 5 | 6 | pub const WHISPER_1: &str = "whisper-1"; 7 | 8 | #[derive(Debug, Serialize, Clone)] 9 | pub struct AudioTranscriptionRequest { 10 | pub model: String, 11 | #[serde(skip_serializing_if = "Option::is_none")] 12 | pub file: Option, 13 | #[serde(skip_serializing_if = "Option::is_none")] 14 | pub bytes: Option>, 15 | pub prompt: Option, 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub response_format: Option, 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub temperature: Option, 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub language: Option, 22 | } 23 | 24 | impl AudioTranscriptionRequest { 25 | pub fn new(file: String, model: String) -> Self { 26 | Self { 27 | model, 28 | file: Some(file), 29 | bytes: None, 30 | prompt: None, 31 | response_format: None, 32 | temperature: None, 33 | language: None, 34 | } 35 | } 36 | 37 | pub fn new_bytes(bytes: Vec, model: String) -> Self { 38 | Self { 39 | model, 40 | file: None, 41 | bytes: Some(bytes), 42 | prompt: None, 43 | response_format: None, 44 | temperature: None, 45 | language: None, 46 | } 47 | } 48 | } 49 | 50 | impl_builder_methods!( 51 | AudioTranscriptionRequest, 52 | prompt: String, 53 | response_format: String, 54 | temperature: f32, 55 | language: String 56 | ); 57 | 58 | #[derive(Debug, Deserialize, Serialize)] 59 | pub struct AudioTranscriptionResponse { 60 | pub text: String, 61 | } 62 | 63 | #[derive(Debug, Serialize, Clone)] 64 | pub struct AudioTranslationRequest { 65 | pub file: String, 66 | pub model: String, 67 | #[serde(skip_serializing_if = "Option::is_none")] 68 | pub prompt: Option, 69 | #[serde(skip_serializing_if = "Option::is_none")] 70 | pub response_format: Option, 71 | #[serde(skip_serializing_if = "Option::is_none")] 72 | pub temperature: Option, 73 | } 74 | 75 | impl AudioTranslationRequest { 76 | pub fn new(file: String, model: String) -> Self { 77 | Self { 78 | file, 79 | model, 80 | prompt: None, 81 | response_format: None, 82 | temperature: None, 83 | } 84 | } 85 | } 86 | 87 | impl_builder_methods!( 88 | AudioTranslationRequest, 89 | prompt: String, 90 | response_format: String, 91 | temperature: f32 92 | ); 93 | 94 | #[derive(Debug, Deserialize, Serialize)] 95 | pub struct AudioTranslationResponse { 96 | pub text: String, 97 | } 98 | 99 | pub const TTS_1: &str = "tts-1"; 100 | pub const TTS_1_HD: &str = "tts-1-hd"; 101 | 102 | pub const VOICE_ALLOY: &str = "alloy"; 103 | pub const VOICE_ECHO: &str = "echo"; 104 | pub const VOICE_FABLE: &str = "fable"; 105 | pub const VOICE_ONYX: &str = "onyx"; 106 | pub const VOICE_NOVA: &str = "nova"; 107 | pub const VOICE_SHIMMER: &str = "shimmer"; 108 | 109 | #[derive(Debug, Serialize, Clone)] 110 | pub struct AudioSpeechRequest { 111 | pub model: String, 112 | pub input: String, 113 | pub voice: String, 114 | pub output: String, 115 | } 116 | 117 | impl AudioSpeechRequest { 118 | pub fn new(model: String, input: String, voice: String, output: String) -> Self { 119 | Self { 120 | model, 121 | input, 122 | voice, 123 | output, 124 | } 125 | } 126 | } 127 | 128 | impl_builder_methods!(AudioSpeechRequest,); 129 | 130 | #[derive(Debug)] 131 | pub struct AudioSpeechResponse { 132 | pub result: bool, 133 | pub headers: Option, 134 | } 135 | -------------------------------------------------------------------------------- /src/v1/batch.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize)] 4 | pub struct CreateBatchRequest { 5 | pub input_file_id: String, 6 | pub endpoint: String, 7 | pub completion_window: String, 8 | #[serde(skip_serializing_if = "Option::is_none")] 9 | pub metadata: Option, 10 | } 11 | 12 | #[derive(Debug, Serialize, Deserialize)] 13 | pub struct Metadata { 14 | pub customer_id: String, 15 | pub batch_description: String, 16 | } 17 | 18 | #[derive(Debug, Serialize, Deserialize)] 19 | pub struct RequestCounts { 20 | pub total: u32, 21 | pub completed: u32, 22 | pub failed: u32, 23 | } 24 | 25 | #[derive(Debug, Serialize, Deserialize)] 26 | pub struct BatchResponse { 27 | pub id: String, 28 | pub object: String, 29 | pub endpoint: String, 30 | pub errors: Option>, 31 | pub input_file_id: String, 32 | pub completion_window: String, 33 | pub status: String, 34 | pub output_file_id: Option, 35 | pub error_file_id: Option, 36 | pub created_at: u64, 37 | pub in_progress_at: Option, 38 | pub expires_at: Option, 39 | pub finalizing_at: Option, 40 | pub completed_at: Option, 41 | pub failed_at: Option, 42 | pub expired_at: Option, 43 | pub cancelling_at: Option, 44 | pub cancelled_at: Option, 45 | pub request_counts: RequestCounts, 46 | pub metadata: Option, 47 | } 48 | 49 | #[derive(Debug, Serialize, Deserialize)] 50 | pub struct ListBatchResponse { 51 | pub object: String, 52 | pub data: Vec, 53 | pub first_id: String, 54 | pub last_id: String, 55 | pub has_more: bool, 56 | } 57 | 58 | impl CreateBatchRequest { 59 | pub fn new(input_file_id: String, endpoint: String, completion_window: String) -> Self { 60 | Self { 61 | input_file_id, 62 | endpoint, 63 | completion_window, 64 | metadata: None, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/v1/chat_completion.rs: -------------------------------------------------------------------------------- 1 | use super::{common, types}; 2 | use crate::impl_builder_methods; 3 | 4 | use serde::de::{self, MapAccess, SeqAccess, Visitor}; 5 | use serde::ser::SerializeMap; 6 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use serde_json::Value; 8 | use std::collections::HashMap; 9 | use std::fmt; 10 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] 11 | pub enum ToolChoiceType { 12 | None, 13 | Auto, 14 | Required, 15 | ToolChoice { tool: Tool }, 16 | } 17 | 18 | #[derive(Debug, Serialize, Deserialize, Clone)] 19 | pub struct ChatCompletionRequest { 20 | pub model: String, 21 | pub messages: Vec, 22 | #[serde(skip_serializing_if = "Option::is_none")] 23 | pub temperature: Option, 24 | #[serde(skip_serializing_if = "Option::is_none")] 25 | pub top_p: Option, 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | pub n: Option, 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | pub response_format: Option, 30 | #[serde(skip_serializing_if = "Option::is_none")] 31 | pub stream: Option, 32 | #[serde(skip_serializing_if = "Option::is_none")] 33 | pub stop: Option>, 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub max_tokens: Option, 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | pub presence_penalty: Option, 38 | #[serde(skip_serializing_if = "Option::is_none")] 39 | pub frequency_penalty: Option, 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub logit_bias: Option>, 42 | #[serde(skip_serializing_if = "Option::is_none")] 43 | pub user: Option, 44 | #[serde(skip_serializing_if = "Option::is_none")] 45 | pub seed: Option, 46 | #[serde(skip_serializing_if = "Option::is_none")] 47 | pub tools: Option>, 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | pub parallel_tool_calls: Option, 50 | #[serde(skip_serializing_if = "Option::is_none")] 51 | #[serde(serialize_with = "serialize_tool_choice")] 52 | pub tool_choice: Option, 53 | } 54 | 55 | impl ChatCompletionRequest { 56 | pub fn new(model: String, messages: Vec) -> Self { 57 | Self { 58 | model, 59 | messages, 60 | temperature: None, 61 | top_p: None, 62 | stream: None, 63 | n: None, 64 | response_format: None, 65 | stop: None, 66 | max_tokens: None, 67 | presence_penalty: None, 68 | frequency_penalty: None, 69 | logit_bias: None, 70 | user: None, 71 | seed: None, 72 | tools: None, 73 | parallel_tool_calls: None, 74 | tool_choice: None, 75 | } 76 | } 77 | } 78 | 79 | impl_builder_methods!( 80 | ChatCompletionRequest, 81 | temperature: f64, 82 | top_p: f64, 83 | n: i64, 84 | response_format: Value, 85 | stream: bool, 86 | stop: Vec, 87 | max_tokens: i64, 88 | presence_penalty: f64, 89 | frequency_penalty: f64, 90 | logit_bias: HashMap, 91 | user: String, 92 | seed: i64, 93 | tools: Vec, 94 | parallel_tool_calls: bool, 95 | tool_choice: ToolChoiceType 96 | ); 97 | 98 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 99 | #[allow(non_camel_case_types)] 100 | pub enum MessageRole { 101 | user, 102 | system, 103 | assistant, 104 | function, 105 | tool, 106 | } 107 | 108 | #[derive(Debug, Clone, PartialEq, Eq)] 109 | pub enum Content { 110 | Text(String), 111 | ImageUrl(Vec), 112 | } 113 | 114 | impl serde::Serialize for Content { 115 | fn serialize(&self, serializer: S) -> Result 116 | where 117 | S: serde::Serializer, 118 | { 119 | match *self { 120 | Content::Text(ref text) => { 121 | if text.is_empty() { 122 | serializer.serialize_none() 123 | } else { 124 | serializer.serialize_str(text) 125 | } 126 | } 127 | Content::ImageUrl(ref image_url) => image_url.serialize(serializer), 128 | } 129 | } 130 | } 131 | 132 | impl<'de> Deserialize<'de> for Content { 133 | fn deserialize(deserializer: D) -> Result 134 | where 135 | D: Deserializer<'de>, 136 | { 137 | struct ContentVisitor; 138 | 139 | impl<'de> Visitor<'de> for ContentVisitor { 140 | type Value = Content; 141 | 142 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 143 | formatter.write_str("a valid content type") 144 | } 145 | 146 | fn visit_str(self, value: &str) -> Result 147 | where 148 | E: de::Error, 149 | { 150 | Ok(Content::Text(value.to_string())) 151 | } 152 | 153 | fn visit_seq(self, seq: A) -> Result 154 | where 155 | A: SeqAccess<'de>, 156 | { 157 | let image_urls: Vec = 158 | Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))?; 159 | Ok(Content::ImageUrl(image_urls)) 160 | } 161 | 162 | fn visit_map(self, map: M) -> Result 163 | where 164 | M: MapAccess<'de>, 165 | { 166 | let image_urls: Vec = 167 | Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?; 168 | Ok(Content::ImageUrl(image_urls)) 169 | } 170 | 171 | fn visit_none(self) -> Result 172 | where 173 | E: de::Error, 174 | { 175 | Ok(Content::Text(String::new())) 176 | } 177 | 178 | fn visit_unit(self) -> Result 179 | where 180 | E: de::Error, 181 | { 182 | Ok(Content::Text(String::new())) 183 | } 184 | } 185 | 186 | deserializer.deserialize_any(ContentVisitor) 187 | } 188 | } 189 | 190 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 191 | #[allow(non_camel_case_types)] 192 | pub enum ContentType { 193 | text, 194 | image_url, 195 | } 196 | 197 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 198 | #[allow(non_camel_case_types)] 199 | pub struct ImageUrlType { 200 | pub url: String, 201 | } 202 | 203 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 204 | #[allow(non_camel_case_types)] 205 | pub struct ImageUrl { 206 | pub r#type: ContentType, 207 | #[serde(skip_serializing_if = "Option::is_none")] 208 | pub text: Option, 209 | #[serde(skip_serializing_if = "Option::is_none")] 210 | pub image_url: Option, 211 | } 212 | 213 | #[derive(Debug, Deserialize, Serialize, Clone)] 214 | pub struct ChatCompletionMessage { 215 | pub role: MessageRole, 216 | pub content: Content, 217 | #[serde(skip_serializing_if = "Option::is_none")] 218 | pub name: Option, 219 | #[serde(skip_serializing_if = "Option::is_none")] 220 | pub tool_calls: Option>, 221 | #[serde(skip_serializing_if = "Option::is_none")] 222 | pub tool_call_id: Option, 223 | } 224 | 225 | #[derive(Debug, Deserialize, Serialize, Clone)] 226 | pub struct ChatCompletionMessageForResponse { 227 | pub role: MessageRole, 228 | #[serde(skip_serializing_if = "Option::is_none")] 229 | pub content: Option, 230 | #[serde(skip_serializing_if = "Option::is_none")] 231 | pub reasoning_content: Option, 232 | #[serde(skip_serializing_if = "Option::is_none")] 233 | pub name: Option, 234 | #[serde(skip_serializing_if = "Option::is_none")] 235 | pub tool_calls: Option>, 236 | } 237 | 238 | #[derive(Debug, Deserialize, Serialize)] 239 | pub struct ChatCompletionChoice { 240 | pub index: i64, 241 | pub message: ChatCompletionMessageForResponse, 242 | pub finish_reason: Option, 243 | pub finish_details: Option, 244 | } 245 | 246 | #[derive(Debug, Deserialize, Serialize)] 247 | pub struct ChatCompletionResponse { 248 | pub id: Option, 249 | pub object: String, 250 | pub created: i64, 251 | pub model: String, 252 | pub choices: Vec, 253 | pub usage: common::Usage, 254 | pub system_fingerprint: Option, 255 | } 256 | 257 | #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] 258 | #[allow(non_camel_case_types)] 259 | pub enum FinishReason { 260 | stop, 261 | length, 262 | content_filter, 263 | tool_calls, 264 | null, 265 | } 266 | 267 | #[derive(Debug, Deserialize, Serialize)] 268 | #[allow(non_camel_case_types)] 269 | pub struct FinishDetails { 270 | pub r#type: FinishReason, 271 | pub stop: String, 272 | } 273 | 274 | #[derive(Debug, Deserialize, Serialize, Clone)] 275 | pub struct ToolCall { 276 | pub id: String, 277 | pub r#type: String, 278 | pub function: ToolCallFunction, 279 | } 280 | 281 | #[derive(Debug, Deserialize, Serialize, Clone)] 282 | pub struct ToolCallFunction { 283 | #[serde(skip_serializing_if = "Option::is_none")] 284 | pub name: Option, 285 | #[serde(skip_serializing_if = "Option::is_none")] 286 | pub arguments: Option, 287 | } 288 | 289 | fn serialize_tool_choice( 290 | value: &Option, 291 | serializer: S, 292 | ) -> Result 293 | where 294 | S: Serializer, 295 | { 296 | match value { 297 | Some(ToolChoiceType::None) => serializer.serialize_str("none"), 298 | Some(ToolChoiceType::Auto) => serializer.serialize_str("auto"), 299 | Some(ToolChoiceType::Required) => serializer.serialize_str("required"), 300 | Some(ToolChoiceType::ToolChoice { tool }) => { 301 | let mut map = serializer.serialize_map(Some(2))?; 302 | map.serialize_entry("type", &tool.r#type)?; 303 | map.serialize_entry("function", &tool.function)?; 304 | map.end() 305 | } 306 | None => serializer.serialize_none(), 307 | } 308 | } 309 | 310 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 311 | pub struct Tool { 312 | pub r#type: ToolType, 313 | pub function: types::Function, 314 | } 315 | 316 | #[derive(Debug, Deserialize, Serialize, Copy, Clone, PartialEq, Eq)] 317 | #[serde(rename_all = "snake_case")] 318 | pub enum ToolType { 319 | Function, 320 | } 321 | -------------------------------------------------------------------------------- /src/v1/common.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Deserialize, Serialize)] 4 | pub struct Usage { 5 | pub prompt_tokens: i32, 6 | pub completion_tokens: i32, 7 | pub total_tokens: i32, 8 | } 9 | 10 | #[derive(Debug, Deserialize, Serialize)] 11 | pub struct DeletionStatus { 12 | pub id: String, 13 | pub object: String, 14 | pub deleted: bool, 15 | } 16 | 17 | #[macro_export] 18 | macro_rules! impl_builder_methods { 19 | ($builder:ident, $($field:ident: $field_type:ty),*) => { 20 | impl $builder { 21 | $( 22 | pub fn $field(mut self, $field: $field_type) -> Self { 23 | self.$field = Some($field); 24 | self 25 | } 26 | )* 27 | } 28 | }; 29 | } 30 | 31 | #[derive(Debug, Serialize, Deserialize)] 32 | pub struct EmptyRequestBody {} 33 | 34 | // https://platform.openai.com/docs/models/o3 35 | pub const O3: &str = "o3"; 36 | pub const O3_2025_04_16: &str = "o3-2025-04-16"; 37 | pub const O3_MINI: &str = "o3-mini"; 38 | pub const O3_MINI_2025_01_31: &str = "o3-mini-2025-01-31"; 39 | 40 | // https://platform.openai.com/docs/models#gpt-4-5 41 | pub const GPT4_5_PREVIEW: &str = "gpt-4.5-preview"; 42 | pub const GPT4_5_PREVIEW_2025_02_27: &str = "gpt-4.5-preview-2025-02-27"; 43 | 44 | // https://platform.openai.com/docs/models/o1 45 | pub const O1_PREVIEW: &str = "o1-preview"; 46 | pub const O1_PREVIEW_2024_09_12: &str = "o1-preview-2024-09-12"; 47 | pub const O1_MINI: &str = "o1-mini"; 48 | pub const O1_MINI_2024_09_12: &str = "o1-mini-2024-09-12"; 49 | 50 | // https://platform.openai.com/docs/models/gpt-4o-mini 51 | pub const GPT4_O_MINI: &str = "gpt-4o-mini"; 52 | pub const GPT4_O_MINI_2024_07_18: &str = "gpt-4o-mini-2024-07-18"; 53 | 54 | // https://platform.openai.com/docs/models/gpt-4o 55 | pub const GPT4_O: &str = "gpt-4o"; 56 | pub const GPT4_O_2024_05_13: &str = "gpt-4o-2024-05-13"; 57 | pub const GPT4_O_2024_08_06: &str = "gpt-4o-2024-08-06"; 58 | pub const GPT4_O_LATEST: &str = "chatgpt-4o-latest"; 59 | 60 | // https://platform.openai.com/docs/models/gpt-3-5 61 | pub const GPT3_5_TURBO_1106: &str = "gpt-3.5-turbo-1106"; 62 | pub const GPT3_5_TURBO: &str = "gpt-3.5-turbo"; 63 | pub const GPT3_5_TURBO_16K: &str = "gpt-3.5-turbo-16k"; 64 | pub const GPT3_5_TURBO_INSTRUCT: &str = "gpt-3.5-turbo-instruct"; 65 | // - legacy 66 | pub const GPT3_5_TURBO_0613: &str = "gpt-3.5-turbo-0613"; 67 | pub const GPT3_5_TURBO_16K_0613: &str = "gpt-3.5-turbo-16k-0613"; 68 | pub const GPT3_5_TURBO_0301: &str = "gpt-3.5-turbo-0301"; 69 | 70 | // https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo 71 | pub const GPT4_0125_PREVIEW: &str = "gpt-4-0125-preview"; 72 | pub const GPT4_TURBO_PREVIEW: &str = "gpt-4-turbo-preview"; 73 | pub const GPT4_1106_PREVIEW: &str = "gpt-4-1106-preview"; 74 | pub const GPT4_VISION_PREVIEW: &str = "gpt-4-vision-preview"; 75 | pub const GPT4: &str = "gpt-4"; 76 | pub const GPT4_32K: &str = "gpt-4-32k"; 77 | pub const GPT4_0613: &str = "gpt-4-0613"; 78 | pub const GPT4_32K_0613: &str = "gpt-4-32k-0613"; 79 | // - legacy 80 | pub const GPT4_0314: &str = "gpt-4-0314"; 81 | pub const GPT4_32K_0314: &str = "gpt-4-32k-0314"; 82 | 83 | // https://platform.openai.com/docs/api-reference/images/object 84 | pub const DALL_E_2: &str = "dall-e-2"; 85 | pub const DALL_E_3: &str = "dall-e-3"; 86 | 87 | // https://platform.openai.com/docs/guides/embeddings/embedding-models 88 | pub const TEXT_EMBEDDING_3_SMALL: &str = "text-embedding-3-small"; 89 | pub const TEXT_EMBEDDING_3_LARGE: &str = "text-embedding-3-large"; 90 | pub const TEXT_EMBEDDING_ADA_002: &str = "text-embedding-ada-002"; 91 | -------------------------------------------------------------------------------- /src/v1/completion.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | use std::option::Option; 4 | 5 | use crate::impl_builder_methods; 6 | use crate::v1::common; 7 | 8 | pub const GPT3_TEXT_DAVINCI_003: &str = "text-davinci-003"; 9 | pub const GPT3_TEXT_DAVINCI_002: &str = "text-davinci-002"; 10 | pub const GPT3_TEXT_CURIE_001: &str = "text-curie-001"; 11 | pub const GPT3_TEXT_BABBAGE_001: &str = "text-babbage-001"; 12 | pub const GPT3_TEXT_ADA_001: &str = "text-ada-001"; 13 | pub const GPT3_TEXT_DAVINCI_001: &str = "text-davinci-001"; 14 | pub const GPT3_DAVINCI_INSTRUCT_BETA: &str = "davinci-instruct-beta"; 15 | pub const GPT3_DAVINCI: &str = "davinci"; 16 | pub const GPT3_CURIE_INSTRUCT_BETA: &str = "curie-instruct-beta"; 17 | pub const GPT3_CURIE: &str = "curie"; 18 | pub const GPT3_ADA: &str = "ada"; 19 | pub const GPT3_BABBAGE: &str = "babbage"; 20 | 21 | #[derive(Debug, Serialize, Deserialize, Clone)] 22 | pub struct CompletionRequest { 23 | pub model: String, 24 | pub prompt: String, 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | pub suffix: Option, 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub max_tokens: Option, 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | pub temperature: Option, 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub top_p: Option, 33 | #[serde(skip_serializing_if = "Option::is_none")] 34 | pub n: Option, 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub stream: Option, 37 | #[serde(skip_serializing_if = "Option::is_none")] 38 | pub logprobs: Option, 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | pub echo: Option, 41 | #[serde(skip_serializing_if = "Option::is_none")] 42 | pub stop: Option>, 43 | #[serde(skip_serializing_if = "Option::is_none")] 44 | pub presence_penalty: Option, 45 | #[serde(skip_serializing_if = "Option::is_none")] 46 | pub frequency_penalty: Option, 47 | #[serde(skip_serializing_if = "Option::is_none")] 48 | pub best_of: Option, 49 | #[serde(skip_serializing_if = "Option::is_none")] 50 | pub logit_bias: Option>, 51 | #[serde(skip_serializing_if = "Option::is_none")] 52 | pub user: Option, 53 | } 54 | 55 | impl CompletionRequest { 56 | pub fn new(model: String, prompt: String) -> Self { 57 | Self { 58 | model, 59 | prompt, 60 | suffix: None, 61 | max_tokens: None, 62 | temperature: None, 63 | top_p: None, 64 | n: None, 65 | stream: None, 66 | logprobs: None, 67 | echo: None, 68 | stop: None, 69 | presence_penalty: None, 70 | frequency_penalty: None, 71 | best_of: None, 72 | logit_bias: None, 73 | user: None, 74 | } 75 | } 76 | } 77 | 78 | impl_builder_methods!( 79 | CompletionRequest, 80 | suffix: String, 81 | max_tokens: i32, 82 | temperature: f32, 83 | top_p: f32, 84 | n: i32, 85 | stream: bool, 86 | logprobs: i32, 87 | echo: bool, 88 | stop: Vec, 89 | presence_penalty: f32, 90 | frequency_penalty: f32, 91 | best_of: i32, 92 | logit_bias: HashMap, 93 | user: String 94 | ); 95 | 96 | #[derive(Debug, Deserialize, Serialize)] 97 | pub struct CompletionChoice { 98 | pub text: String, 99 | pub index: i64, 100 | pub finish_reason: String, 101 | pub logprobs: Option, 102 | } 103 | 104 | #[derive(Debug, Deserialize, Serialize)] 105 | pub struct LogprobResult { 106 | pub tokens: Vec, 107 | pub token_logprobs: Vec, 108 | pub top_logprobs: Vec>, 109 | pub text_offset: Vec, 110 | } 111 | 112 | #[derive(Debug, Deserialize, Serialize)] 113 | pub struct CompletionResponse { 114 | pub id: String, 115 | pub object: String, 116 | pub created: i64, 117 | pub model: String, 118 | pub choices: Vec, 119 | pub usage: common::Usage, 120 | } 121 | -------------------------------------------------------------------------------- /src/v1/edit.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::option::Option; 3 | 4 | use crate::impl_builder_methods; 5 | use crate::v1::common; 6 | 7 | #[derive(Debug, Serialize, Clone)] 8 | pub struct EditRequest { 9 | pub model: String, 10 | #[serde(skip_serializing_if = "Option::is_none")] 11 | pub input: Option, 12 | pub instruction: String, 13 | #[serde(skip_serializing_if = "Option::is_none")] 14 | pub n: Option, 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub temperature: Option, 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | pub top_p: Option, 19 | } 20 | 21 | impl EditRequest { 22 | pub fn new(model: String, instruction: String) -> Self { 23 | Self { 24 | model, 25 | instruction, 26 | input: None, 27 | n: None, 28 | temperature: None, 29 | top_p: None, 30 | } 31 | } 32 | } 33 | 34 | impl_builder_methods!( 35 | EditRequest, 36 | input: String, 37 | n: i32, 38 | temperature: f32, 39 | top_p: f32 40 | ); 41 | 42 | #[derive(Debug, Deserialize, Serialize)] 43 | pub struct EditChoice { 44 | pub text: String, 45 | pub index: i32, 46 | } 47 | 48 | #[derive(Debug, Deserialize, Serialize)] 49 | pub struct EditResponse { 50 | pub object: String, 51 | pub created: i64, 52 | pub usage: common::Usage, 53 | pub choices: Vec, 54 | } 55 | -------------------------------------------------------------------------------- /src/v1/embedding.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::option::Option; 3 | 4 | use crate::impl_builder_methods; 5 | 6 | #[derive(Debug, Deserialize, Serialize)] 7 | pub struct EmbeddingData { 8 | pub object: String, 9 | pub embedding: Vec, 10 | pub index: i32, 11 | } 12 | 13 | #[derive(Debug, Serialize, Deserialize, Clone)] 14 | #[serde(rename_all = "lowercase")] 15 | pub enum EncodingFormat { 16 | Float, 17 | Base64, 18 | } 19 | 20 | #[derive(Debug, Serialize, Clone, Deserialize)] 21 | pub struct EmbeddingRequest { 22 | pub model: String, 23 | pub input: Vec, 24 | pub encoding_format: Option, 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | pub dimensions: Option, 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub user: Option, 29 | } 30 | 31 | impl EmbeddingRequest { 32 | pub fn new(model: String, input: Vec) -> Self { 33 | Self { 34 | model, 35 | input, 36 | encoding_format: None, 37 | dimensions: None, 38 | user: None, 39 | } 40 | } 41 | } 42 | 43 | impl_builder_methods!( 44 | EmbeddingRequest, 45 | user: String 46 | ); 47 | 48 | #[derive(Debug, Deserialize, Serialize)] 49 | pub struct EmbeddingResponse { 50 | pub object: String, 51 | pub data: Vec, 52 | pub model: String, 53 | pub usage: Usage, 54 | } 55 | 56 | #[derive(Debug, Deserialize, Serialize)] 57 | pub struct Usage { 58 | pub prompt_tokens: i32, 59 | pub total_tokens: i32, 60 | } 61 | -------------------------------------------------------------------------------- /src/v1/error.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{self}; 2 | use std::error::Error; 3 | use std::fmt; 4 | 5 | #[derive(Debug)] 6 | pub enum APIError { 7 | ReqwestError(reqwest::Error), 8 | CustomError { message: String }, 9 | } 10 | 11 | impl fmt::Display for APIError { 12 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 13 | match self { 14 | APIError::ReqwestError(err) => write!(f, "ReqwestError: {}", err), 15 | APIError::CustomError { message } => write!(f, "APIError: {}", message), 16 | } 17 | } 18 | } 19 | 20 | impl Error for APIError {} 21 | 22 | impl From for APIError { 23 | fn from(err: reqwest::Error) -> APIError { 24 | APIError::ReqwestError(err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/v1/file.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Deserialize, Serialize)] 4 | pub struct FileData { 5 | pub id: String, 6 | pub object: String, 7 | pub bytes: i32, 8 | pub created_at: i64, 9 | pub filename: String, 10 | pub purpose: String, 11 | } 12 | 13 | #[derive(Debug, Deserialize, Serialize)] 14 | pub struct FileListResponse { 15 | pub object: String, 16 | pub data: Vec, 17 | } 18 | 19 | #[derive(Debug, Serialize)] 20 | pub struct FileUploadRequest { 21 | pub file: String, 22 | pub purpose: String, 23 | } 24 | 25 | impl FileUploadRequest { 26 | pub fn new(file: String, purpose: String) -> Self { 27 | Self { file, purpose } 28 | } 29 | } 30 | 31 | #[derive(Debug, Deserialize, Serialize)] 32 | pub struct FileUploadResponse { 33 | pub id: String, 34 | pub object: String, 35 | pub bytes: i32, 36 | pub created_at: i64, 37 | pub filename: String, 38 | pub purpose: String, 39 | } 40 | 41 | #[derive(Debug, Serialize)] 42 | pub struct FileDeleteRequest { 43 | pub file_id: String, 44 | } 45 | 46 | impl FileDeleteRequest { 47 | pub fn new(file_id: String) -> Self { 48 | Self { file_id } 49 | } 50 | } 51 | 52 | #[derive(Debug, Deserialize, Serialize)] 53 | pub struct FileDeleteResponse { 54 | pub id: String, 55 | pub object: String, 56 | pub delete: bool, 57 | } 58 | 59 | #[derive(Debug, Deserialize, Serialize)] 60 | pub struct FileRetrieveResponse { 61 | pub id: String, 62 | pub object: String, 63 | pub bytes: i32, 64 | pub created_at: i64, 65 | pub filename: String, 66 | pub purpose: String, 67 | } 68 | -------------------------------------------------------------------------------- /src/v1/fine_tuning.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::impl_builder_methods; 4 | 5 | #[derive(Debug, Serialize, Clone)] 6 | pub struct CreateFineTuningJobRequest { 7 | pub model: String, 8 | pub training_file: String, 9 | #[serde(skip_serializing_if = "Option::is_none")] 10 | pub hyperparameters: Option, 11 | #[serde(skip_serializing_if = "Option::is_none")] 12 | pub suffix: Option, 13 | #[serde(skip_serializing_if = "Option::is_none")] 14 | pub validation_file: Option, 15 | } 16 | 17 | impl CreateFineTuningJobRequest { 18 | pub fn new(model: String, training_file: String) -> Self { 19 | Self { 20 | model, 21 | training_file, 22 | hyperparameters: None, 23 | suffix: None, 24 | validation_file: None, 25 | } 26 | } 27 | } 28 | 29 | impl_builder_methods!( 30 | CreateFineTuningJobRequest, 31 | hyperparameters: HyperParameters, 32 | suffix: String, 33 | validation_file: String 34 | ); 35 | 36 | #[derive(Debug, Serialize)] 37 | pub struct ListFineTuningJobsRequest { 38 | // TODO pass as query params 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | pub after: Option, 41 | #[serde(skip_serializing_if = "Option::is_none")] 42 | pub limit: Option, 43 | } 44 | 45 | impl ListFineTuningJobsRequest { 46 | pub fn new(_fine_tune_id: String) -> Self { 47 | Self { 48 | after: None, 49 | limit: None, 50 | } 51 | } 52 | } 53 | 54 | #[derive(Debug, Serialize)] 55 | pub struct ListFineTuningJobEventsRequest { 56 | pub fine_tuning_job_id: String, 57 | // TODO pass as query params 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub after: Option, 60 | #[serde(skip_serializing_if = "Option::is_none")] 61 | pub limit: Option, 62 | } 63 | 64 | impl ListFineTuningJobEventsRequest { 65 | pub fn new(fine_tuning_job_id: String) -> Self { 66 | Self { 67 | fine_tuning_job_id, 68 | after: None, 69 | limit: None, 70 | } 71 | } 72 | } 73 | 74 | #[derive(Debug, Serialize)] 75 | pub struct RetrieveFineTuningJobRequest { 76 | pub fine_tuning_job_id: String, 77 | } 78 | 79 | impl RetrieveFineTuningJobRequest { 80 | pub fn new(fine_tuning_job_id: String) -> Self { 81 | Self { fine_tuning_job_id } 82 | } 83 | } 84 | 85 | #[derive(Debug, Serialize)] 86 | pub struct CancelFineTuningJobRequest { 87 | pub fine_tuning_job_id: String, 88 | } 89 | 90 | impl CancelFineTuningJobRequest { 91 | pub fn new(fine_tuning_job_id: String) -> Self { 92 | Self { fine_tuning_job_id } 93 | } 94 | } 95 | 96 | #[derive(Debug, Deserialize, Serialize)] 97 | pub struct FineTuningPagination { 98 | pub object: String, 99 | pub data: Vec, 100 | pub has_more: bool, 101 | } 102 | 103 | #[derive(Debug, Deserialize, Serialize)] 104 | pub struct FineTuningJobObject { 105 | pub id: String, 106 | pub created_at: i64, 107 | pub error: Option, 108 | pub fine_tuned_model: Option, 109 | pub finished_at: Option, 110 | pub hyperparameters: HyperParameters, 111 | pub model: String, 112 | pub object: String, 113 | pub organization_id: String, 114 | pub result_files: Vec, 115 | pub status: String, 116 | pub trained_tokens: Option, 117 | pub training_file: String, 118 | pub validation_file: Option, 119 | } 120 | 121 | #[derive(Debug, Deserialize, Serialize)] 122 | pub struct FineTuningJobError { 123 | pub code: String, 124 | pub message: String, 125 | pub param: Option, 126 | } 127 | 128 | #[derive(Debug, Deserialize, Serialize)] 129 | pub struct FineTuningJobEvent { 130 | pub id: String, 131 | pub created_at: i64, 132 | pub level: String, 133 | pub message: String, 134 | pub object: String, 135 | } 136 | 137 | #[derive(Clone, Debug, Deserialize, Serialize)] 138 | pub struct HyperParameters { 139 | #[serde(skip_serializing_if = "Option::is_none")] 140 | pub batch_size: Option, 141 | #[serde(skip_serializing_if = "Option::is_none")] 142 | pub learning_rate_multiplier: Option, 143 | #[serde(skip_serializing_if = "Option::is_none")] 144 | pub n_epochs: Option, 145 | } 146 | -------------------------------------------------------------------------------- /src/v1/image.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::option::Option; 3 | 4 | use crate::impl_builder_methods; 5 | 6 | #[derive(Debug, Deserialize, Serialize)] 7 | pub struct ImageData { 8 | pub url: String, 9 | } 10 | 11 | #[derive(Debug, Serialize, Clone, Deserialize)] 12 | pub struct ImageGenerationRequest { 13 | pub prompt: String, 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub model: Option, 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub n: Option, 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub size: Option, 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub response_format: Option, 22 | #[serde(skip_serializing_if = "Option::is_none")] 23 | pub user: Option, 24 | } 25 | 26 | impl ImageGenerationRequest { 27 | pub fn new(prompt: String) -> Self { 28 | Self { 29 | prompt, 30 | model: None, 31 | n: None, 32 | size: None, 33 | response_format: None, 34 | user: None, 35 | } 36 | } 37 | } 38 | 39 | impl_builder_methods!( 40 | ImageGenerationRequest, 41 | model: String, 42 | n: i32, 43 | size: String, 44 | response_format: String, 45 | user: String 46 | ); 47 | 48 | #[derive(Debug, Deserialize, Serialize)] 49 | pub struct ImageGenerationResponse { 50 | pub created: i64, 51 | pub data: Vec, 52 | } 53 | 54 | #[derive(Debug, Serialize, Clone)] 55 | pub struct ImageEditRequest { 56 | pub image: String, 57 | #[serde(skip_serializing_if = "Option::is_none")] 58 | pub mask: Option, 59 | pub prompt: String, 60 | #[serde(skip_serializing_if = "Option::is_none")] 61 | pub model: Option, 62 | #[serde(skip_serializing_if = "Option::is_none")] 63 | pub n: Option, 64 | #[serde(skip_serializing_if = "Option::is_none")] 65 | pub size: Option, 66 | #[serde(skip_serializing_if = "Option::is_none")] 67 | pub response_format: Option, 68 | #[serde(skip_serializing_if = "Option::is_none")] 69 | pub user: Option, 70 | } 71 | 72 | impl ImageEditRequest { 73 | pub fn new(image: String, prompt: String) -> Self { 74 | Self { 75 | image, 76 | prompt, 77 | mask: None, 78 | model: None, 79 | n: None, 80 | size: None, 81 | response_format: None, 82 | user: None, 83 | } 84 | } 85 | } 86 | 87 | impl_builder_methods!( 88 | ImageEditRequest, 89 | mask: String, 90 | model: String, 91 | n: i32, 92 | size: String, 93 | response_format: String, 94 | user: String 95 | ); 96 | 97 | #[derive(Debug, Deserialize, Serialize)] 98 | pub struct ImageEditResponse { 99 | pub created: i64, 100 | pub data: Vec, 101 | } 102 | 103 | #[derive(Debug, Serialize, Clone)] 104 | pub struct ImageVariationRequest { 105 | pub image: String, 106 | #[serde(skip_serializing_if = "Option::is_none")] 107 | pub n: Option, 108 | #[serde(skip_serializing_if = "Option::is_none")] 109 | pub model: Option, 110 | #[serde(skip_serializing_if = "Option::is_none")] 111 | pub size: Option, 112 | #[serde(skip_serializing_if = "Option::is_none")] 113 | pub response_format: Option, 114 | #[serde(skip_serializing_if = "Option::is_none")] 115 | pub user: Option, 116 | } 117 | 118 | impl ImageVariationRequest { 119 | pub fn new(image: String) -> Self { 120 | Self { 121 | image, 122 | model: None, 123 | n: None, 124 | size: None, 125 | response_format: None, 126 | user: None, 127 | } 128 | } 129 | } 130 | 131 | impl_builder_methods!( 132 | ImageVariationRequest, 133 | model: String, 134 | n: i32, 135 | size: String, 136 | response_format: String, 137 | user: String 138 | ); 139 | 140 | #[derive(Debug, Deserialize, Serialize)] 141 | pub struct ImageVariationResponse { 142 | pub created: i64, 143 | pub data: Vec, 144 | } 145 | -------------------------------------------------------------------------------- /src/v1/message.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | use crate::impl_builder_methods; 5 | 6 | #[derive(Debug, Serialize, Clone)] 7 | pub struct CreateMessageRequest { 8 | pub role: MessageRole, 9 | pub content: String, 10 | #[serde(skip_serializing_if = "Option::is_none")] 11 | pub attachments: Option>, 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub metadata: Option>, 14 | } 15 | 16 | impl CreateMessageRequest { 17 | pub fn new(role: MessageRole, content: String) -> Self { 18 | Self { 19 | role, 20 | content, 21 | attachments: None, 22 | metadata: None, 23 | } 24 | } 25 | } 26 | 27 | impl_builder_methods!( 28 | CreateMessageRequest, 29 | attachments: Vec, 30 | metadata: HashMap 31 | ); 32 | 33 | #[derive(Debug, Serialize, Clone)] 34 | pub struct ModifyMessageRequest { 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub metadata: Option>, 37 | } 38 | 39 | impl ModifyMessageRequest { 40 | pub fn new() -> Self { 41 | Self { metadata: None } 42 | } 43 | } 44 | 45 | impl Default for ModifyMessageRequest { 46 | fn default() -> Self { 47 | Self::new() 48 | } 49 | } 50 | 51 | impl_builder_methods!( 52 | ModifyMessageRequest, 53 | metadata: HashMap 54 | ); 55 | 56 | #[derive(Debug, Deserialize, Serialize)] 57 | pub struct MessageObject { 58 | pub id: String, 59 | pub object: String, 60 | pub created_at: i64, 61 | pub thread_id: String, 62 | pub role: MessageRole, 63 | pub content: Vec, 64 | #[serde(skip_serializing_if = "Option::is_none")] 65 | pub assistant_id: Option, 66 | #[serde(skip_serializing_if = "Option::is_none")] 67 | pub run_id: Option, 68 | #[serde(skip_serializing_if = "Option::is_none")] 69 | pub attachments: Option>, 70 | pub metadata: Option>, 71 | } 72 | 73 | #[derive(Serialize, Deserialize, Debug, Clone)] 74 | pub struct Attachment { 75 | pub file_id: Option, 76 | pub tools: Vec, 77 | } 78 | 79 | #[derive(Serialize, Deserialize, Debug, Clone)] 80 | pub struct Tool { 81 | pub r#type: String, 82 | } 83 | 84 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 85 | #[allow(non_camel_case_types)] 86 | pub enum MessageRole { 87 | user, 88 | system, 89 | assistant, 90 | function, 91 | } 92 | 93 | #[derive(Debug, Deserialize, Serialize)] 94 | pub struct Content { 95 | #[serde(rename = "type")] 96 | pub content_type: String, 97 | pub text: ContentText, 98 | } 99 | 100 | #[derive(Debug, Deserialize, Serialize)] 101 | pub struct ContentText { 102 | pub value: String, 103 | pub annotations: Vec, 104 | } 105 | 106 | #[derive(Debug, Deserialize, Serialize)] 107 | pub struct ListMessage { 108 | pub object: String, 109 | pub data: Vec, 110 | pub first_id: String, 111 | pub last_id: String, 112 | pub has_more: bool, 113 | } 114 | 115 | #[derive(Debug, Deserialize, Serialize)] 116 | pub struct MessageFileObject { 117 | pub id: String, 118 | pub object: String, 119 | pub created_at: i64, 120 | pub message_id: String, 121 | } 122 | 123 | #[derive(Debug, Deserialize, Serialize)] 124 | pub struct ListMessageFile { 125 | pub object: String, 126 | pub data: Vec, 127 | pub first_id: String, 128 | pub last_id: String, 129 | pub has_more: bool, 130 | } 131 | 132 | #[derive(Debug, Deserialize, Serialize)] 133 | #[serde(tag = "type")] 134 | #[serde(rename_all = "snake_case")] 135 | pub enum ContentTextAnnotations { 136 | FileCitation(ContentTextAnnotationsFileCitationObject), 137 | FilePath(ContentTextAnnotationsFilePathObject), 138 | } 139 | 140 | #[derive(Debug, Deserialize, Serialize)] 141 | pub struct ContentTextAnnotationsFileCitationObject { 142 | pub text: String, 143 | pub file_citation: FileCitation, 144 | pub start_index: u32, 145 | pub end_index: u32, 146 | } 147 | 148 | #[derive(Debug, Deserialize, Serialize)] 149 | pub struct FileCitation { 150 | pub file_id: String, 151 | pub quote: Option, 152 | } 153 | 154 | #[derive(Debug, Deserialize, Serialize)] 155 | pub struct ContentTextAnnotationsFilePathObject { 156 | pub text: String, 157 | pub file_path: FilePath, 158 | pub start_index: u32, 159 | pub end_index: u32, 160 | } 161 | 162 | #[derive(Debug, Deserialize, Serialize)] 163 | pub struct FilePath { 164 | pub file_id: String, 165 | } 166 | -------------------------------------------------------------------------------- /src/v1/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod common; 2 | pub mod error; 3 | pub mod types; 4 | 5 | pub mod audio; 6 | pub mod batch; 7 | pub mod chat_completion; 8 | pub mod completion; 9 | pub mod edit; 10 | pub mod embedding; 11 | pub mod file; 12 | pub mod fine_tuning; 13 | pub mod image; 14 | pub mod model; 15 | pub mod moderation; 16 | 17 | // beta 18 | pub mod assistant; 19 | pub mod message; 20 | pub mod run; 21 | pub mod thread; 22 | 23 | pub mod api; 24 | -------------------------------------------------------------------------------- /src/v1/model.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Deserialize, Serialize)] 4 | pub struct ModelsResponse { 5 | pub object: String, 6 | pub data: Vec, 7 | } 8 | 9 | #[derive(Debug, Deserialize, Serialize)] 10 | pub struct ModelResponse { 11 | pub id: String, 12 | pub object: String, 13 | pub created: i64, 14 | pub owned_by: String, 15 | } 16 | -------------------------------------------------------------------------------- /src/v1/moderation.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::impl_builder_methods; 4 | 5 | #[derive(Debug, Serialize, Clone)] 6 | pub struct CreateModerationRequest { 7 | pub input: String, 8 | #[serde(skip_serializing_if = "Option::is_none")] 9 | pub model: Option, 10 | } 11 | 12 | impl CreateModerationRequest { 13 | pub fn new(input: String) -> Self { 14 | Self { input, model: None } 15 | } 16 | } 17 | 18 | impl_builder_methods!( 19 | CreateModerationRequest, 20 | model: String 21 | ); 22 | 23 | #[derive(Debug, Deserialize, Serialize)] 24 | pub struct CreateModerationResponse { 25 | pub id: String, 26 | pub model: String, 27 | pub results: Vec, 28 | } 29 | 30 | #[derive(Debug, Deserialize, Serialize)] 31 | pub struct ModerationResult { 32 | pub categories: ModerationCategories, 33 | pub category_scores: ModerationCategoryScores, 34 | pub flagged: bool, 35 | } 36 | 37 | #[derive(Debug, Deserialize, Serialize)] 38 | pub struct ModerationCategories { 39 | #[serde(rename = "hate")] 40 | pub is_hate: bool, 41 | #[serde(rename = "hate/threatening")] 42 | pub is_hate_threatening: bool, 43 | #[serde(rename = "self-harm")] 44 | pub is_self_harm: bool, 45 | pub sexual: bool, 46 | #[serde(rename = "sexual/minors")] 47 | pub is_sexual_minors: bool, 48 | pub violence: bool, 49 | #[serde(rename = "violence/graphic")] 50 | pub is_violence_graphic: bool, 51 | } 52 | 53 | #[derive(Debug, Deserialize, Serialize)] 54 | pub struct ModerationCategoryScores { 55 | #[serde(rename = "hate")] 56 | pub hate_score: f64, 57 | #[serde(rename = "hate/threatening")] 58 | pub hate_threatening_score: f64, 59 | #[serde(rename = "self-harm")] 60 | pub self_harm_score: f64, 61 | pub sexual: f64, 62 | #[serde(rename = "sexual/minors")] 63 | pub sexual_minors_score: f64, 64 | pub violence: f64, 65 | #[serde(rename = "violence/graphic")] 66 | pub violence_graphic_score: f64, 67 | } 68 | -------------------------------------------------------------------------------- /src/v1/run.rs: -------------------------------------------------------------------------------- 1 | use super::thread::CreateThreadRequest; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_json::Value; 4 | use std::collections::HashMap; 5 | 6 | use super::types; 7 | use crate::impl_builder_methods; 8 | 9 | #[derive(Debug, Serialize, Clone)] 10 | pub struct CreateRunRequest { 11 | assistant_id: String, 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub model: Option, 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub instructions: Option, 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub tools: Option>>, 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub metadata: Option>, 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub response_format: Option, // 1: json!("auto"), 2: json!({"type": "json_object"}) 22 | } 23 | 24 | impl CreateRunRequest { 25 | pub fn new(assistant_id: String) -> Self { 26 | Self { 27 | assistant_id, 28 | model: None, 29 | instructions: None, 30 | tools: None, 31 | metadata: None, 32 | response_format: None, 33 | } 34 | } 35 | } 36 | 37 | impl_builder_methods!( 38 | CreateRunRequest, 39 | model: String, 40 | instructions: String, 41 | tools: Vec>, 42 | metadata: HashMap, 43 | response_format: Value 44 | ); 45 | 46 | #[derive(Debug, Serialize, Clone)] 47 | pub struct ModifyRunRequest { 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | pub metadata: Option>, 50 | } 51 | 52 | impl ModifyRunRequest { 53 | pub fn new() -> Self { 54 | Self { metadata: None } 55 | } 56 | } 57 | 58 | impl Default for ModifyRunRequest { 59 | fn default() -> Self { 60 | Self::new() 61 | } 62 | } 63 | 64 | impl_builder_methods!( 65 | ModifyRunRequest, 66 | metadata: HashMap 67 | ); 68 | 69 | #[derive(Debug, Deserialize, Serialize, Clone)] 70 | pub struct LastError { 71 | pub code: String, 72 | pub message: String, 73 | } 74 | 75 | #[derive(Debug, Deserialize, Serialize)] 76 | pub struct RunObject { 77 | pub id: String, 78 | pub object: String, 79 | pub created_at: i64, 80 | pub thread_id: String, 81 | pub assistant_id: String, 82 | pub status: String, 83 | #[serde(skip_serializing_if = "Option::is_none")] 84 | pub required_action: Option>, 85 | #[serde(skip_serializing_if = "Option::is_none")] 86 | pub last_error: Option, 87 | #[serde(skip_serializing_if = "Option::is_none")] 88 | pub expires_at: Option, 89 | #[serde(skip_serializing_if = "Option::is_none")] 90 | pub started_at: Option, 91 | #[serde(skip_serializing_if = "Option::is_none")] 92 | pub cancelled_at: Option, 93 | #[serde(skip_serializing_if = "Option::is_none")] 94 | pub failed_at: Option, 95 | #[serde(skip_serializing_if = "Option::is_none")] 96 | pub completed_at: Option, 97 | pub model: String, 98 | pub instructions: Option, 99 | pub tools: Vec, 100 | pub metadata: HashMap, 101 | } 102 | 103 | #[derive(Debug, Deserialize, Serialize)] 104 | pub struct ListRun { 105 | pub object: String, 106 | pub data: Vec, 107 | pub first_id: String, 108 | pub last_id: String, 109 | pub has_more: bool, 110 | } 111 | 112 | #[derive(Debug, Serialize, Clone)] 113 | pub struct CreateThreadAndRunRequest { 114 | pub assistant_id: String, 115 | #[serde(skip_serializing_if = "Option::is_none")] 116 | pub thread: Option, 117 | #[serde(skip_serializing_if = "Option::is_none")] 118 | pub model: Option, 119 | #[serde(skip_serializing_if = "Option::is_none")] 120 | pub instructions: Option, 121 | #[serde(skip_serializing_if = "Option::is_none")] 122 | pub tools: Option>>, 123 | #[serde(skip_serializing_if = "Option::is_none")] 124 | pub metadata: Option>, 125 | } 126 | 127 | #[derive(Debug, Deserialize, Serialize, Clone)] 128 | pub struct RunStepObject { 129 | pub id: String, 130 | pub object: String, 131 | pub created_at: i64, 132 | pub assistant_id: String, 133 | pub thread_id: String, 134 | pub run_id: String, 135 | #[serde(rename = "type")] 136 | pub run_step_type: String, 137 | pub status: String, 138 | pub step_details: HashMap, 139 | #[serde(skip_serializing_if = "Option::is_none")] 140 | pub last_error: Option, 141 | #[serde(skip_serializing_if = "Option::is_none")] 142 | pub expires_at: Option, 143 | #[serde(skip_serializing_if = "Option::is_none")] 144 | pub started_at: Option, 145 | #[serde(skip_serializing_if = "Option::is_none")] 146 | pub cancelled_at: Option, 147 | #[serde(skip_serializing_if = "Option::is_none")] 148 | pub failed_at: Option, 149 | #[serde(skip_serializing_if = "Option::is_none")] 150 | pub completed_at: Option, 151 | pub metadata: HashMap, 152 | } 153 | 154 | #[derive(Debug, Deserialize, Serialize, Clone)] 155 | pub struct ListRunStep { 156 | pub object: String, 157 | pub data: Vec, 158 | pub first_id: String, 159 | pub last_id: String, 160 | pub has_more: bool, 161 | } 162 | -------------------------------------------------------------------------------- /src/v1/thread.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | use crate::impl_builder_methods; 5 | 6 | #[derive(Debug, Serialize, Clone)] 7 | pub struct CreateThreadRequest { 8 | #[serde(skip_serializing_if = "Option::is_none")] 9 | pub messages: Option>, 10 | #[serde(skip_serializing_if = "Option::is_none")] 11 | pub tool_resources: Option, 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub metadata: Option>, 14 | } 15 | 16 | #[derive(Debug, Deserialize, Serialize, Clone)] 17 | pub struct ToolResource { 18 | pub code_interpreter: Option, 19 | pub file_search: Option, 20 | } 21 | 22 | #[derive(Debug, Deserialize, Serialize, Clone)] 23 | pub struct CodeInterpreter { 24 | pub file_ids: Option>, 25 | } 26 | 27 | #[derive(Debug, Deserialize, Serialize, Clone)] 28 | pub struct FileSearch { 29 | pub vector_store_ids: Option>, 30 | pub vector_stores: Option, 31 | } 32 | 33 | #[derive(Debug, Deserialize, Serialize, Clone)] 34 | pub struct VectorStores { 35 | pub file_ids: Option>, 36 | pub chunking_strategy: Option, 37 | pub metadata: Option>, 38 | } 39 | 40 | impl CreateThreadRequest { 41 | pub fn new() -> Self { 42 | Self { 43 | messages: None, 44 | tool_resources: None, 45 | metadata: None, 46 | } 47 | } 48 | } 49 | 50 | impl Default for CreateThreadRequest { 51 | fn default() -> Self { 52 | Self::new() 53 | } 54 | } 55 | 56 | impl_builder_methods!( 57 | CreateThreadRequest, 58 | messages: Vec, 59 | tool_resources: ToolResource 60 | ); 61 | 62 | #[derive(Debug, Deserialize, Serialize)] 63 | pub struct ThreadObject { 64 | pub id: String, 65 | pub object: String, 66 | pub created_at: i64, 67 | pub metadata: HashMap, 68 | #[serde(skip_serializing_if = "Option::is_none")] 69 | pub tool_resources: Option, 70 | } 71 | 72 | #[derive(Debug, Deserialize, Serialize, Clone)] 73 | pub struct Message { 74 | pub id: String, 75 | pub object: String, 76 | pub created_at: i64, 77 | pub thread_id: String, 78 | pub role: MessageRole, 79 | pub content: Vec, 80 | #[serde(skip_serializing_if = "Option::is_none")] 81 | pub assistant_id: Option, 82 | #[serde(skip_serializing_if = "Option::is_none")] 83 | pub run_id: Option, 84 | #[serde(skip_serializing_if = "Option::is_none")] 85 | pub attachments: Option>, 86 | pub metadata: Option>, 87 | } 88 | 89 | #[derive(Debug, Deserialize, Serialize, Clone)] 90 | pub struct Content { 91 | #[serde(rename = "type")] 92 | pub content_type: String, 93 | pub text: ContentText, 94 | } 95 | 96 | #[derive(Debug, Deserialize, Serialize, Clone)] 97 | pub struct ContentText { 98 | pub value: String, 99 | pub annotations: Vec, 100 | } 101 | 102 | #[derive(Serialize, Deserialize, Debug, Clone)] 103 | pub struct Attachment { 104 | pub file_id: String, 105 | pub tools: Vec, 106 | } 107 | 108 | #[derive(Serialize, Deserialize, Debug, Clone)] 109 | pub struct Tool { 110 | pub r#type: String, 111 | } 112 | 113 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 114 | #[allow(non_camel_case_types)] 115 | pub enum MessageRole { 116 | user, 117 | system, 118 | assistant, 119 | function, 120 | } 121 | 122 | #[derive(Debug, Serialize, Clone)] 123 | pub struct ModifyThreadRequest { 124 | #[serde(skip_serializing_if = "Option::is_none")] 125 | pub metadata: Option>, 126 | } 127 | 128 | impl ModifyThreadRequest { 129 | pub fn new() -> Self { 130 | Self { metadata: None } 131 | } 132 | } 133 | 134 | impl Default for ModifyThreadRequest { 135 | fn default() -> Self { 136 | Self::new() 137 | } 138 | } 139 | 140 | impl_builder_methods!( 141 | ModifyThreadRequest, 142 | metadata: HashMap 143 | ); 144 | -------------------------------------------------------------------------------- /src/v1/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 5 | pub struct Function { 6 | pub name: String, 7 | #[serde(skip_serializing_if = "Option::is_none")] 8 | pub description: Option, 9 | pub parameters: FunctionParameters, 10 | } 11 | 12 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 13 | pub struct FunctionParameters { 14 | #[serde(rename = "type")] 15 | pub schema_type: JSONSchemaType, 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub properties: Option>>, 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub required: Option>, 20 | } 21 | 22 | #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] 23 | #[serde(rename_all = "lowercase")] 24 | pub enum JSONSchemaType { 25 | Object, 26 | Number, 27 | String, 28 | Array, 29 | Null, 30 | Boolean, 31 | } 32 | 33 | #[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)] 34 | pub struct JSONSchemaDefine { 35 | #[serde(rename = "type")] 36 | pub schema_type: Option, 37 | #[serde(skip_serializing_if = "Option::is_none")] 38 | pub description: Option, 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | pub enum_values: Option>, 41 | #[serde(skip_serializing_if = "Option::is_none")] 42 | pub properties: Option>>, 43 | #[serde(skip_serializing_if = "Option::is_none")] 44 | pub required: Option>, 45 | #[serde(skip_serializing_if = "Option::is_none")] 46 | pub items: Option>, 47 | } 48 | 49 | #[derive(Debug, Deserialize, Serialize, Clone)] 50 | #[serde(tag = "type")] 51 | #[serde(rename_all = "snake_case")] 52 | pub enum Tools { 53 | CodeInterpreter, 54 | FileSearch(ToolsFileSearch), 55 | Function(ToolsFunction), 56 | } 57 | 58 | #[derive(Debug, Deserialize, Serialize, Clone)] 59 | pub struct ToolsFileSearch { 60 | #[serde(skip_serializing_if = "Option::is_none")] 61 | pub file_search: Option, 62 | } 63 | 64 | #[derive(Debug, Deserialize, Serialize, Clone)] 65 | pub struct ToolsFunction { 66 | pub function: Function, 67 | } 68 | 69 | #[derive(Debug, Deserialize, Serialize, Clone)] 70 | pub struct ToolsFileSearchObject { 71 | pub max_num_results: Option, 72 | pub ranking_options: Option, 73 | } 74 | 75 | #[derive(Debug, Deserialize, Serialize, Clone)] 76 | pub struct FileSearchRankingOptions { 77 | pub ranker: Option, 78 | pub score_threshold: Option, 79 | } 80 | 81 | #[derive(Debug, Deserialize, Serialize, Clone)] 82 | pub enum FileSearchRanker { 83 | #[serde(rename = "auto")] 84 | Auto, 85 | #[serde(rename = "default_2024_08_21")] 86 | Default2024_08_21, 87 | } 88 | --------------------------------------------------------------------------------