Skip to main content

rig_core/providers/copilot/
extension.rs

1//! GitHub Copilot's typed reply extras, on Chat Completions and Responses
2//! alike. Copilot has no typed request option.
3//!
4//! ```
5//! use rig_core::completion::CompletionResponse;
6//! use rig_core::providers::copilot::extension::CopilotExt;
7//!
8//! fn billed(reply: &CompletionResponse) -> Option<u64> {
9//!     reply.extras_lossy::<CopilotExt>().copilot_usage?.total_nano_aiu
10//! }
11//! # let _ = billed;
12//! ```
13
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16
17use crate::completion::provider_options::reply_field;
18use crate::completion::{ExtensionOptions, ProviderExtension, ReplyExtras};
19use crate::message::Api;
20
21/// Copilot's extension marker.
22#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
23pub struct CopilotExt;
24
25impl ProviderExtension for CopilotExt {
26    const PROVIDER: &'static str = super::PROVIDER_NAME;
27    type Options = CopilotOptions;
28    type Extras = CopilotExtras;
29}
30
31/// Copilot's request options: none.
32#[non_exhaustive]
33#[derive(Clone, Debug, Default, PartialEq, Serialize)]
34pub struct CopilotOptions {}
35
36impl CopilotOptions {
37    /// No option set.
38    pub fn new() -> Self {
39        Self::default()
40    }
41}
42
43impl ExtensionOptions for CopilotOptions {
44    type Ext = CopilotExt;
45}
46
47/// Copilot's reply fields. Each is `None` when the reply lacks it.
48#[non_exhaustive]
49#[derive(Clone, Debug, Default, PartialEq)]
50pub struct CopilotExtras {
51    /// What the request was billed. Both routes.
52    pub copilot_usage: Option<CopilotUsage>,
53    /// The content filter's verdicts on the prompt. Chat only.
54    pub prompt_filter_results: Option<Vec<Value>>,
55}
56
57/// What a Copilot request was billed.
58#[non_exhaustive]
59#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
60pub struct CopilotUsage {
61    /// The tokens billed, by kind.
62    #[serde(default)]
63    pub token_details: Option<Vec<TokenDetail>>,
64    /// The total, in billionths of an AI unit.
65    #[serde(default)]
66    pub total_nano_aiu: Option<u64>,
67}
68
69/// The tokens of one kind a request was billed.
70#[non_exhaustive]
71#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
72pub struct TokenDetail {
73    /// The kind, such as `input`, `output` or `cache_read`.
74    #[serde(default)]
75    pub token_type: Option<String>,
76    /// How many tokens.
77    #[serde(default)]
78    pub token_count: Option<u64>,
79    /// The tokens one price applies to.
80    #[serde(default)]
81    pub batch_size: Option<u64>,
82    /// The price per batch, in billionths of an AI unit.
83    #[serde(default)]
84    pub cost_per_batch: Option<u64>,
85}
86
87impl ReplyExtras for CopilotExtras {
88    fn from_reply(_api: &Api, raw: &Value) -> Result<Self, serde_json::Error> {
89        Ok(Self {
90            copilot_usage: reply_field(raw, "/copilot_usage")?,
91            prompt_filter_results: reply_field(raw, "/prompt_filter_results")?,
92        })
93    }
94}
95
96#[cfg(test)]
97mod tests;