1use crate::{
2 auth::{BotToken, CredentialKind, Credentials, SessionToken},
3 config::ClientConfig,
4 error::KagiError,
5 official_api::OfficialApi,
6 routing::ProtocolSurface,
7 session_web::SessionWeb,
8 transport::Transport,
9};
10
11#[derive(Debug, Clone)]
12pub struct KagiClient {
13 transport: Transport,
14 credentials: Credentials,
15}
16
17impl KagiClient {
18 pub fn builder() -> KagiClientBuilder {
19 KagiClientBuilder::default()
20 }
21
22 pub fn new(credentials: Credentials, config: ClientConfig) -> Result<Self, KagiError> {
23 let transport = Transport::new(config)?;
24 Ok(Self {
25 transport,
26 credentials,
27 })
28 }
29
30 pub fn with_bot_token(token: BotToken) -> Result<Self, KagiError> {
31 Self::new(Credentials::from(token), ClientConfig::default())
32 }
33
34 pub fn with_session_token(token: SessionToken) -> Result<Self, KagiError> {
35 Self::new(Credentials::from(token), ClientConfig::default())
36 }
37
38 pub fn official_api(&self) -> Result<OfficialApi<'_>, KagiError> {
39 self.ensure_surface_access(ProtocolSurface::OfficialApi)?;
40 Ok(OfficialApi::new(self))
41 }
42
43 pub fn session_web(&self) -> Result<SessionWeb<'_>, KagiError> {
44 self.ensure_surface_access(ProtocolSurface::SessionWeb)?;
45 Ok(SessionWeb::new(self))
46 }
47
48 pub(crate) fn transport(&self) -> &Transport {
49 &self.transport
50 }
51
52 pub(crate) fn credentials(&self) -> &Credentials {
53 &self.credentials
54 }
55
56 fn ensure_surface_access(&self, surface: ProtocolSurface) -> Result<(), KagiError> {
57 let provided = self.credentials.kind();
58 let expected = match surface {
59 ProtocolSurface::OfficialApi => CredentialKind::BotToken,
60 ProtocolSurface::SessionWeb => CredentialKind::SessionToken,
61 };
62
63 if provided != expected {
64 return Err(KagiError::UnsupportedAuthSurface {
65 surface,
66 credential: provided,
67 expected,
68 });
69 }
70
71 Ok(())
72 }
73}
74
75#[derive(Debug, Clone, Default)]
76pub struct KagiClientBuilder {
77 config: ClientConfig,
78 credentials: Option<Credentials>,
79 credential_conflict: Option<(CredentialKind, CredentialKind)>,
80}
81
82impl KagiClientBuilder {
83 pub fn config(mut self, config: ClientConfig) -> Self {
84 self.config = config;
85 self
86 }
87
88 pub fn bot_token(mut self, token: BotToken) -> Self {
89 self.set_credentials(Credentials::from(token));
90 self
91 }
92
93 pub fn session_token(mut self, token: SessionToken) -> Self {
94 self.set_credentials(Credentials::from(token));
95 self
96 }
97
98 pub fn credentials(mut self, credentials: Credentials) -> Self {
99 self.set_credentials(credentials);
100 self
101 }
102
103 pub fn build(self) -> Result<KagiClient, KagiError> {
104 if let Some((already_set, attempted)) = self.credential_conflict {
105 return Err(KagiError::ConflictingCredentialConfiguration {
106 already_set,
107 attempted,
108 });
109 }
110
111 let credentials =
112 self.credentials
113 .ok_or_else(|| KagiError::MissingCredentialConfiguration {
114 reason: "set bot_token(...) or session_token(...) before build()".to_string(),
115 })?;
116
117 KagiClient::new(credentials, self.config)
118 }
119
120 fn set_credentials(&mut self, next_credentials: Credentials) {
121 let attempted = next_credentials.kind();
122
123 if let Some(current_credentials) = &self.credentials {
124 let already_set = current_credentials.kind();
125 if already_set != attempted {
126 self.credential_conflict = Some((already_set, attempted));
127 return;
128 }
129 }
130
131 self.credentials = Some(next_credentials);
132 }
133}