1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
impl Client {
/// Sends a pre-built message to the AI model.
///
/// This method allows sending messages with images or custom content blocks
/// that cannot be expressed as simple text prompts. Use the `Message` helper
/// methods like [`user_with_image()`](Message::user_with_image),
/// [`user_with_image_detail()`](Message::user_with_image_detail), or
/// [`user_with_base64_image()`](Message::user_with_base64_image) to create
/// messages with multimodal content.
///
/// Unlike [`send()`](Client::send), this method:
/// - Accepts pre-built `Message` objects instead of text prompts
/// - Bypasses `UserPromptSubmit` hooks (since message is already constructed)
/// - Enables multimodal interactions (text + images)
///
/// After calling this method, use [`receive()`](Client::receive) to get the
/// response content blocks.
///
/// # Arguments
///
/// * `message` - A pre-built message (typically created with `Message::user_with_image()` or similar helpers)
///
/// # Errors
///
/// Returns `Error` if:
/// - Network request fails
/// - Server returns an error
/// - Response cannot be parsed
/// - Request is interrupted via [`interrupt()`](Client::interrupt)
///
/// # Example
///
/// ```rust,no_run
/// use open_agent::{Client, AgentOptions, Message, ImageDetail};
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let options = AgentOptions::builder()
/// .model("gpt-4-vision-preview")
/// .base_url("http://localhost:1234/v1")
/// .build()?;
///
/// let mut client = Client::new(options)?;
///
/// // Send a message with an image
/// let msg = Message::user_with_image(
/// "What's in this image?",
/// "https://example.com/photo.jpg"
/// )?;
/// client.send_message(msg).await?;
///
/// // Receive the response
/// while let Some(block) = client.receive().await? {
/// // Process response blocks
/// }
/// # Ok(())
/// # }
/// ```
pub async fn send_message(&mut self, message: Message) -> Result<()> {
// Reset interrupt flag for new query
// This allows the client to be reused after a previous interruption
// Uses SeqCst ordering to ensure visibility across all threads
self.interrupted.store(false, Ordering::SeqCst);
// Discard any leftover manual-mode blocks from an abandoned stream.
self.manual_receive_buffer.clear();
self.current_stream = None;
// Note: We do NOT run UserPromptSubmit hooks here because:
// 1. The message is already fully constructed
// 2. Hooks expect string prompts, not complex Message objects
// 3. For multimodal messages, there's no single "prompt" to modify
// Add message to history BEFORE sending request
// This ensures history consistency even if request fails
self.history.push(message);
self.start_request().await
}
}