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