├── .gitattributes
├── .gitignore
├── Directory.Build.props
├── GemiNet.slnx
├── LICENSE
├── README.md
├── README_JA.md
├── sandbox
└── ConsoleApp1
│ ├── .gitignore
│ ├── ConsoleApp1.csproj
│ └── Program.cs
└── src
├── GemiNet.Extensions.AI
├── .gitignore
├── GemiNet.Extensions.AI.csproj
├── GoogleGenAIChatClient.cs
├── GoogleGenAIEmbeddingGenerator.cs
├── GoogleGenAIExtensions.cs
└── ModelConverter.cs
└── GemiNet
├── .gitignore
├── GemiNet.csproj
├── GemiNetException.cs
├── GemiNetJsonSerializerContext.cs
├── GenerateContentResponseExtensions.cs
├── GoogleGenAI.cs
├── ICaches.cs
├── IFiles.cs
├── ILive.cs
├── IModels.cs
├── Internal
├── Mime.g.cs
├── Mime.tt
├── PolyfillJsonStringEnumConverter.cs
├── PooledList.cs
└── mime.json
├── JsonSerializerContextExtensions.cs
├── LiveSession.cs
├── Models.cs
└── Models
├── Caching.cs
├── Embeddings.cs
├── Files.cs
├── GeneratingContent.cs
├── Live.cs
├── Models.cs
└── Tokens.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | *.DS_Store
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 1.0.3
6 | nuskey
7 | © Yusuke Nakada
8 | https://github.com/nuskey8/GemiNet
9 | $(PackageProjectUrl)
10 | git
11 | MIT
12 | README.md
13 |
14 |
15 |
--------------------------------------------------------------------------------
/GemiNet.slnx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Yusuke Nakada
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 | # GemiNet
2 | Gemini Developer API client for .NET and Unity
3 |
4 | [](https://www.nuget.org/packages/GemiNet)
5 | [](https://github.com/nuskey8/GemiNet/releases)
6 | [](./LICENSE)
7 |
8 | English | [日本語](./README_JA.md)
9 |
10 | ## Overview
11 |
12 | GemiNet is a Gemini Developer API client library for .NET/Unity. It is designed based on the official TypeScript SDK ([js-genai](https://github.com/googleapis/js-genai)), and provides an easy-to-use API compared to other libraries ([Google_GenerativeAI](https://github.com/gunpal5/Google_GenerativeAI), [GeminiSharp](https://github.com/dprakash2101/GeminiSharp), etc.). GemiNet also offers extension packages compatible with Microsoft.Extensions.AI abstraction layer, enabling smoother integration with your applications.
13 |
14 | ## Installation
15 |
16 | ### NuGet packages
17 |
18 | GemiNet requires .NET Standard 2.1 or later. The package is available on NuGet.
19 |
20 | ### .NET CLI
21 |
22 | ```ps1
23 | dotnet add package GemiNet
24 | ```
25 |
26 | ### Package Manager
27 |
28 | ```ps1
29 | Install-Package GemiNet
30 | ```
31 |
32 | ### Unity
33 |
34 | You can use GemiNet in Unity by using [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity). For details, please refer to the NuGetForUnity README.
35 |
36 | ## Quick Start
37 |
38 | You can call the Gemini API using `GoogleGenAI`.
39 |
40 | ```cs
41 | using GemiNet;
42 |
43 | using var ai = new GoogleGenAI
44 | {
45 | ApiKey = Environment.GetEnvironmentVariable("GEMINI_API_KEY"),
46 | };
47 |
48 | var response = await ai.Models.GenerateContentAsync(new()
49 | {
50 | Model = Models.Gemini2_0Flash, // models/gemini-2.0-flash
51 | Contents = "Hello, Gemini!"
52 | });
53 |
54 | Console.WriteLine(response.GetText());
55 | ```
56 |
57 | Streaming generation is also supported.
58 |
59 | ```cs
60 | var request = new GenerateContentRequest
61 | {
62 | Model = Models.Gemini2_0Flash,
63 | Contents = "Hello, Gemini!"
64 | };
65 |
66 | await foreach (var response in GenerateContentStreamAsync(request))
67 | {
68 | Console.WriteLine(response.GetText());
69 | }
70 | ```
71 |
72 | ## Features
73 |
74 | GemiNet's `GoogleGenAI` is modularized similar to the TypeScript SDK.
75 |
76 | * `ai.Models` allows you to generate content, generate vectors, and retrieve available models.
77 | * `ai.Caches` lets you create caches for specific inputs.
78 | * `ai.Files` enables file uploads and deletions.
79 | * `ai.Live` supports the Live API (bidirectional communication via WebSocket). For details, see the [Live API](#live-api) section.
80 |
81 | ## Live API
82 |
83 | GemiNet supports the [Live API](https://ai.google.dev/api/live). You can read messages using `ReceiveAsync()` with `await foreach`.
84 |
85 | ```cs
86 | using GemiNet;
87 |
88 | using var ai = new GoogleGenAI();
89 |
90 | await using var session = await ai.Live.ConnectAsync(new()
91 | {
92 | Model = Models.Gemini2_0FlashLive,
93 | Config = new()
94 | {
95 | ResponseModalities = [Modality.Text]
96 | }
97 | });
98 |
99 | _ = Task.Run(async () =>
100 | {
101 | await session.SendRealtimeInputAsync(new()
102 | {
103 | Text = "Hello, Gemini!"
104 | });
105 | });
106 |
107 | await foreach (var message in session.ReceiveAsync())
108 | {
109 | Console.WriteLine(message.ServerContent?.ModelTurn?.Parts[0].Text);
110 | }
111 | ```
112 |
113 | ## Microsoft.Extensions.AI
114 |
115 | To use GemiNet with Microsoft.Extensions.AI, add the `GemiNet.Extensions.AI` package.
116 |
117 | ### Installation
118 |
119 | #### .NET CLI
120 |
121 | ```ps1
122 | dotnet add package GemiNet.Extensions.AI
123 | ```
124 |
125 | #### Package Manager
126 |
127 | ```ps1
128 | Install-Package GemiNet.Extensions.AI
129 | ```
130 |
131 | ### Usage
132 |
133 | You can convert `GoogleGenAI` to Microsoft.Extensions.AI interfaces using `AsChatClient()` and `AsEmbeddingGenerator()`.
134 |
135 | ```cs
136 | using GemiNet;
137 | using GemiNet.Extensions.AI;
138 |
139 | using var ai = new GoogleGenAI();
140 |
141 | var client = ai.AsChatClient(Models.Gemini2_0Flash);
142 | var response = await client.GetResponseAsync([new(ChatRole.User, "What is AI?")]);
143 |
144 | var embeddingGenerator = ai.AsEmbeddingGenerator(Models.Gemini2_0Flash);
145 | var embedding = await embeddingGenerator.GenerateEmbeddingAsync("Hello, Gemini!");
146 | ```
147 |
148 | ## Limitations
149 |
150 | * This library does not currently support Vertex AI. If you want to use Vertex AI, please use [Google.Cloud.AIPlatform.V1](https://www.nuget.org/packages/Google.Cloud.AIPlatform.V1/) instead.
151 |
152 | ## License
153 |
154 | This library is released under the [MIT License](./LICENSE).
155 |
--------------------------------------------------------------------------------
/README_JA.md:
--------------------------------------------------------------------------------
1 | # GemiNet
2 | Gemini Developer API client for .NET and Unity
3 |
4 | [](https://www.nuget.org/packages/GemiNet)
5 | [](https://github.com/nuskey8/GemiNet/releases)
6 | [](./LICENSE)
7 |
8 | [English](./README.md) | 日本語
9 |
10 | ## 概要
11 |
12 | GemiNetはNET/Unity向けのGemini Developer APIクライアントライブラリです。GemiNetは公式のTypeScript SDK([js-genai](https://github.com/googleapis/js-genai))のAPIに基づいて設計されており、他のライブラリ([Google_GenerativeAI](https://github.com/gunpal5/Google_GenerativeAI), [GeminiSharp](https://github.com/dprakash2101/GeminiSharp), etc.)と比較して扱いやすいAPIを提供します。また、Microsoft.Extensions.AIの抽象化層に対応した拡張パッケージを提供することで、アプリケーションとの統合をよりスムーズに行えるようになっています。
13 |
14 | ## インストール
15 |
16 | ### NuGet packages
17 |
18 | GemiNetを利用するには.NET Standard2.1以上が必要です。パッケージはNuGetから入手できます。
19 |
20 | ### .NET CLI
21 |
22 | ```ps1
23 | dotnet add package GemiNet
24 | ```
25 |
26 | ### Package Manager
27 |
28 | ```ps1
29 | Install-Package GemiNet
30 | ```
31 |
32 | ### Unity
33 |
34 | [NugetForUnity](https://github.com/GlitchEnzo/NuGetForUnity)を用いることでUnityでGemiNetを利用可能です。詳細はNuGetForUnityのREADMEを参照してください。
35 |
36 | ## クイックスタート
37 |
38 | `GoogleGenAI`を用いてGemini APIを呼び出すことが可能です。
39 |
40 | ```cs
41 | using GemiNet;
42 |
43 | using var ai = new GoogleGenAI
44 | {
45 | ApiKey = Environment.GetEnvironmentVariable("GEMINI_API_KEY"),
46 | };
47 |
48 | var response = await ai.Models.GenerateContentAsync(new()
49 | {
50 | Model = Models.Gemini2_0Flash, // models/gemini-2.0-flash
51 | Contents = "Hello, Gemini!"
52 | });
53 |
54 | Console.WriteLine(response.GetText());
55 | ```
56 |
57 | また、ストリーミング生成にも対応しています。
58 |
59 | ```cs
60 | var request = new GenerateContentRequest
61 | {
62 | Model = Models.Gemini2_0Flash,
63 | Contents = "Hello, Gemini!"
64 | };
65 |
66 | await foreach (var response in GenerateContentStreamAsync(request))
67 | {
68 | Console.WriteLine(response.GetText());
69 | }
70 | ```
71 |
72 | ## 機能一覧
73 |
74 | GemiNetの`GoogleGenAI`はTypeScript SDKと同様のモジュールで分割されています。
75 |
76 | * `ai.Models`ではコンテンツの生成やベクトル生成、利用可能なモデルの取得などを行えます。
77 | * `ai.Caches`では特定の入力のキャッシュを作成することができます。
78 | * `ai.Files`ではファイルのアップロードや削除などを行えます。
79 | * `ai.Live`ではLive API(WebSocketを用いた双方向通信)を利用できます。詳細は[Live API](#live-api)の項目を参照してください。
80 |
81 | ## Live API
82 |
83 | GemiNetは[Live API](https://ai.google.dev/api/live)に対応しています。メッセージは`ReceiveAsync()`を用いることで`await foreach`で読み取ることが可能です。
84 |
85 | ```cs
86 | using GemiNet;
87 |
88 | using var ai = new GoogleGenAI();
89 |
90 | await using var session = await ai.Live.ConnectAsync(new()
91 | {
92 | Model = Models.Gemini2_0FlashLive,
93 | Config = new()
94 | {
95 | ResponseModalities = [Modality.Text]
96 | }
97 | });
98 |
99 | _ = Task.Run(async () =>
100 | {
101 | await session.SendRealtimeInputAsync(new()
102 | {
103 | Text = "Hello, Gemini!"
104 | });
105 | });
106 |
107 | await foreach (var message in session.ReceiveAsync())
108 | {
109 | Console.WriteLine(message.ServerContent?.ModelTurn?.Parts[0].Text);
110 | }
111 | ```
112 |
113 | ## Microsoft.Extensions.AI
114 |
115 | GemiNetをMicrosoft.Extensions.AIで利用するには`GemiNet.Extensions.AI`パッケージを追加します。
116 |
117 | ### インストール
118 |
119 | #### .NET CLI
120 |
121 | ```ps1
122 | dotnet add package GemiNet.Extensions.AI
123 | ```
124 |
125 | #### Package Manager
126 |
127 | ```ps1
128 | Install-Package GemiNet.Extensions.AI
129 | ```
130 |
131 | ### 使い方
132 |
133 | `AsChatClient()`と`AsEmbeddingGenerator()`を用いて`GoogleGenAI`をMicrosoft.Extensions.AIのインターフェースに変換できます。
134 |
135 | ```cs
136 | using GemiNet;
137 | using GemiNet.Extensions.AI;
138 |
139 | using var ai = new GoogleGenAI();
140 |
141 | var client = ai.AsChatClient(Models.Gemini2_0Flash);
142 | var response = await client.GetResponseAsync([new(ChatRole.User, "What is AI?")]);
143 |
144 | var embeddingGenerator = ai.AsEmbeddingGenerator(Models.Gemini2_0Flash);
145 | var embedding = await embeddingGenerator.GenerateEmbeddingAsync("Hello, Gemini!");
146 | ```
147 |
148 | ## 制限事項
149 |
150 | * このライブラリは現在Vertex AIに対応していません。Vertex AIを利用したい場合は[Google.Cloud.AIPlatform.V1](https://www.nuget.org/packages/Google.Cloud.AIPlatform.V1/)を代わりに使用してください。
151 |
152 | ## ライセンス
153 |
154 | このライブラリは[MITライセンス](./LICENSE)の下に公開されています。
--------------------------------------------------------------------------------
/sandbox/ConsoleApp1/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from `dotnet new gitignore`
5 |
6 | # dotenv files
7 | .env
8 |
9 | # User-specific files
10 | *.rsuser
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # User-specific files (MonoDevelop/Xamarin Studio)
17 | *.userprefs
18 |
19 | # Mono auto generated files
20 | mono_crash.*
21 |
22 | # Build results
23 | [Dd]ebug/
24 | [Dd]ebugPublic/
25 | [Rr]elease/
26 | [Rr]eleases/
27 | x64/
28 | x86/
29 | [Ww][Ii][Nn]32/
30 | [Aa][Rr][Mm]/
31 | [Aa][Rr][Mm]64/
32 | bld/
33 | [Bb]in/
34 | [Oo]bj/
35 | [Ll]og/
36 | [Ll]ogs/
37 |
38 | # Visual Studio 2015/2017 cache/options directory
39 | .vs/
40 | # Uncomment if you have tasks that create the project's static files in wwwroot
41 | #wwwroot/
42 |
43 | # Visual Studio 2017 auto generated files
44 | Generated\ Files/
45 |
46 | # MSTest test Results
47 | [Tt]est[Rr]esult*/
48 | [Bb]uild[Ll]og.*
49 |
50 | # NUnit
51 | *.VisualState.xml
52 | TestResult.xml
53 | nunit-*.xml
54 |
55 | # Build Results of an ATL Project
56 | [Dd]ebugPS/
57 | [Rr]eleasePS/
58 | dlldata.c
59 |
60 | # Benchmark Results
61 | BenchmarkDotNet.Artifacts/
62 |
63 | # .NET
64 | project.lock.json
65 | project.fragment.lock.json
66 | artifacts/
67 |
68 | # Tye
69 | .tye/
70 |
71 | # ASP.NET Scaffolding
72 | ScaffoldingReadMe.txt
73 |
74 | # StyleCop
75 | StyleCopReport.xml
76 |
77 | # Files built by Visual Studio
78 | *_i.c
79 | *_p.c
80 | *_h.h
81 | *.ilk
82 | *.meta
83 | *.obj
84 | *.iobj
85 | *.pch
86 | *.pdb
87 | *.ipdb
88 | *.pgc
89 | *.pgd
90 | *.rsp
91 | *.sbr
92 | *.tlb
93 | *.tli
94 | *.tlh
95 | *.tmp
96 | *.tmp_proj
97 | *_wpftmp.csproj
98 | *.log
99 | *.tlog
100 | *.vspscc
101 | *.vssscc
102 | .builds
103 | *.pidb
104 | *.svclog
105 | *.scc
106 |
107 | # Chutzpah Test files
108 | _Chutzpah*
109 |
110 | # Visual C++ cache files
111 | ipch/
112 | *.aps
113 | *.ncb
114 | *.opendb
115 | *.opensdf
116 | *.sdf
117 | *.cachefile
118 | *.VC.db
119 | *.VC.VC.opendb
120 |
121 | # Visual Studio profiler
122 | *.psess
123 | *.vsp
124 | *.vspx
125 | *.sap
126 |
127 | # Visual Studio Trace Files
128 | *.e2e
129 |
130 | # TFS 2012 Local Workspace
131 | $tf/
132 |
133 | # Guidance Automation Toolkit
134 | *.gpState
135 |
136 | # ReSharper is a .NET coding add-in
137 | _ReSharper*/
138 | *.[Rr]e[Ss]harper
139 | *.DotSettings.user
140 |
141 | # TeamCity is a build add-in
142 | _TeamCity*
143 |
144 | # DotCover is a Code Coverage Tool
145 | *.dotCover
146 |
147 | # AxoCover is a Code Coverage Tool
148 | .axoCover/*
149 | !.axoCover/settings.json
150 |
151 | # Coverlet is a free, cross platform Code Coverage Tool
152 | coverage*.json
153 | coverage*.xml
154 | coverage*.info
155 |
156 | # Visual Studio code coverage results
157 | *.coverage
158 | *.coveragexml
159 |
160 | # NCrunch
161 | _NCrunch_*
162 | .*crunch*.local.xml
163 | nCrunchTemp_*
164 |
165 | # MightyMoose
166 | *.mm.*
167 | AutoTest.Net/
168 |
169 | # Web workbench (sass)
170 | .sass-cache/
171 |
172 | # Installshield output folder
173 | [Ee]xpress/
174 |
175 | # DocProject is a documentation generator add-in
176 | DocProject/buildhelp/
177 | DocProject/Help/*.HxT
178 | DocProject/Help/*.HxC
179 | DocProject/Help/*.hhc
180 | DocProject/Help/*.hhk
181 | DocProject/Help/*.hhp
182 | DocProject/Help/Html2
183 | DocProject/Help/html
184 |
185 | # Click-Once directory
186 | publish/
187 |
188 | # Publish Web Output
189 | *.[Pp]ublish.xml
190 | *.azurePubxml
191 | # Note: Comment the next line if you want to checkin your web deploy settings,
192 | # but database connection strings (with potential passwords) will be unencrypted
193 | *.pubxml
194 | *.publishproj
195 |
196 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
197 | # checkin your Azure Web App publish settings, but sensitive information contained
198 | # in these scripts will be unencrypted
199 | PublishScripts/
200 |
201 | # NuGet Packages
202 | *.nupkg
203 | # NuGet Symbol Packages
204 | *.snupkg
205 | # The packages folder can be ignored because of Package Restore
206 | **/[Pp]ackages/*
207 | # except build/, which is used as an MSBuild target.
208 | !**/[Pp]ackages/build/
209 | # Uncomment if necessary however generally it will be regenerated when needed
210 | #!**/[Pp]ackages/repositories.config
211 | # NuGet v3's project.json files produces more ignorable files
212 | *.nuget.props
213 | *.nuget.targets
214 |
215 | # Microsoft Azure Build Output
216 | csx/
217 | *.build.csdef
218 |
219 | # Microsoft Azure Emulator
220 | ecf/
221 | rcf/
222 |
223 | # Windows Store app package directories and files
224 | AppPackages/
225 | BundleArtifacts/
226 | Package.StoreAssociation.xml
227 | _pkginfo.txt
228 | *.appx
229 | *.appxbundle
230 | *.appxupload
231 |
232 | # Visual Studio cache files
233 | # files ending in .cache can be ignored
234 | *.[Cc]ache
235 | # but keep track of directories ending in .cache
236 | !?*.[Cc]ache/
237 |
238 | # Others
239 | ClientBin/
240 | ~$*
241 | *~
242 | *.dbmdl
243 | *.dbproj.schemaview
244 | *.jfm
245 | *.pfx
246 | *.publishsettings
247 | orleans.codegen.cs
248 |
249 | # Including strong name files can present a security risk
250 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
251 | #*.snk
252 |
253 | # Since there are multiple workflows, uncomment next line to ignore bower_components
254 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
255 | #bower_components/
256 |
257 | # RIA/Silverlight projects
258 | Generated_Code/
259 |
260 | # Backup & report files from converting an old project file
261 | # to a newer Visual Studio version. Backup files are not needed,
262 | # because we have git ;-)
263 | _UpgradeReport_Files/
264 | Backup*/
265 | UpgradeLog*.XML
266 | UpgradeLog*.htm
267 | ServiceFabricBackup/
268 | *.rptproj.bak
269 |
270 | # SQL Server files
271 | *.mdf
272 | *.ldf
273 | *.ndf
274 |
275 | # Business Intelligence projects
276 | *.rdl.data
277 | *.bim.layout
278 | *.bim_*.settings
279 | *.rptproj.rsuser
280 | *- [Bb]ackup.rdl
281 | *- [Bb]ackup ([0-9]).rdl
282 | *- [Bb]ackup ([0-9][0-9]).rdl
283 |
284 | # Microsoft Fakes
285 | FakesAssemblies/
286 |
287 | # GhostDoc plugin setting file
288 | *.GhostDoc.xml
289 |
290 | # Node.js Tools for Visual Studio
291 | .ntvs_analysis.dat
292 | node_modules/
293 |
294 | # Visual Studio 6 build log
295 | *.plg
296 |
297 | # Visual Studio 6 workspace options file
298 | *.opt
299 |
300 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
301 | *.vbw
302 |
303 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
304 | *.vbp
305 |
306 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
307 | *.dsw
308 | *.dsp
309 |
310 | # Visual Studio 6 technical files
311 | *.ncb
312 | *.aps
313 |
314 | # Visual Studio LightSwitch build output
315 | **/*.HTMLClient/GeneratedArtifacts
316 | **/*.DesktopClient/GeneratedArtifacts
317 | **/*.DesktopClient/ModelManifest.xml
318 | **/*.Server/GeneratedArtifacts
319 | **/*.Server/ModelManifest.xml
320 | _Pvt_Extensions
321 |
322 | # Paket dependency manager
323 | .paket/paket.exe
324 | paket-files/
325 |
326 | # FAKE - F# Make
327 | .fake/
328 |
329 | # CodeRush personal settings
330 | .cr/personal
331 |
332 | # Python Tools for Visual Studio (PTVS)
333 | __pycache__/
334 | *.pyc
335 |
336 | # Cake - Uncomment if you are using it
337 | # tools/**
338 | # !tools/packages.config
339 |
340 | # Tabs Studio
341 | *.tss
342 |
343 | # Telerik's JustMock configuration file
344 | *.jmconfig
345 |
346 | # BizTalk build output
347 | *.btp.cs
348 | *.btm.cs
349 | *.odx.cs
350 | *.xsd.cs
351 |
352 | # OpenCover UI analysis results
353 | OpenCover/
354 |
355 | # Azure Stream Analytics local run output
356 | ASALocalRun/
357 |
358 | # MSBuild Binary and Structured Log
359 | *.binlog
360 |
361 | # NVidia Nsight GPU debugger configuration file
362 | *.nvuser
363 |
364 | # MFractors (Xamarin productivity tool) working folder
365 | .mfractor/
366 |
367 | # Local History for Visual Studio
368 | .localhistory/
369 |
370 | # Visual Studio History (VSHistory) files
371 | .vshistory/
372 |
373 | # BeatPulse healthcheck temp database
374 | healthchecksdb
375 |
376 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
377 | MigrationBackup/
378 |
379 | # Ionide (cross platform F# VS Code tools) working folder
380 | .ionide/
381 |
382 | # Fody - auto-generated XML schema
383 | FodyWeavers.xsd
384 |
385 | # VS Code files for those working on multiple tools
386 | .vscode/*
387 | !.vscode/settings.json
388 | !.vscode/tasks.json
389 | !.vscode/launch.json
390 | !.vscode/extensions.json
391 | *.code-workspace
392 |
393 | # Local History for Visual Studio Code
394 | .history/
395 |
396 | # Windows Installer files from build outputs
397 | *.cab
398 | *.msi
399 | *.msix
400 | *.msm
401 | *.msp
402 |
403 | # JetBrains Rider
404 | *.sln.iml
405 | .idea/
406 |
407 | ##
408 | ## Visual studio for Mac
409 | ##
410 |
411 |
412 | # globs
413 | Makefile.in
414 | *.userprefs
415 | *.usertasks
416 | config.make
417 | config.status
418 | aclocal.m4
419 | install-sh
420 | autom4te.cache/
421 | *.tar.gz
422 | tarballs/
423 | test-results/
424 |
425 | # Mac bundle stuff
426 | *.dmg
427 | *.app
428 |
429 | # content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
430 | # General
431 | .DS_Store
432 | .AppleDouble
433 | .LSOverride
434 |
435 | # Icon must end with two \r
436 | Icon
437 |
438 |
439 | # Thumbnails
440 | ._*
441 |
442 | # Files that might appear in the root of a volume
443 | .DocumentRevisions-V100
444 | .fseventsd
445 | .Spotlight-V100
446 | .TemporaryItems
447 | .Trashes
448 | .VolumeIcon.icns
449 | .com.apple.timemachine.donotpresent
450 |
451 | # Directories potentially created on remote AFP share
452 | .AppleDB
453 | .AppleDesktop
454 | Network Trash Folder
455 | Temporary Items
456 | .apdisk
457 |
458 | # content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
459 | # Windows thumbnail cache files
460 | Thumbs.db
461 | ehthumbs.db
462 | ehthumbs_vista.db
463 |
464 | # Dump file
465 | *.stackdump
466 |
467 | # Folder config file
468 | [Dd]esktop.ini
469 |
470 | # Recycle Bin used on file shares
471 | $RECYCLE.BIN/
472 |
473 | # Windows Installer files
474 | *.cab
475 | *.msi
476 | *.msix
477 | *.msm
478 | *.msp
479 |
480 | # Windows shortcuts
481 | *.lnk
482 |
483 | # Vim temporary swap files
484 | *.swp
485 |
486 | Properties/launchSettings.json
--------------------------------------------------------------------------------
/sandbox/ConsoleApp1/ConsoleApp1.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Exe
9 | net9.0
10 | enable
11 | enable
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/sandbox/ConsoleApp1/Program.cs:
--------------------------------------------------------------------------------
1 | using GemiNet;
2 |
3 | var ai = new GoogleGenAI();
4 |
5 | var response = await ai.Models.GenerateContentAsync(new()
6 | {
7 | Model = Models.Gemini1_5Flash,
8 | Contents = "Hello, Gemini!"
9 | });
10 |
11 | Console.WriteLine(response.GetText());
12 |
13 | await foreach (var model in ai.Models.ListAsync())
14 | {
15 | Console.WriteLine(model.Name);
16 | }
--------------------------------------------------------------------------------
/src/GemiNet.Extensions.AI/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from `dotnet new gitignore`
5 |
6 | # dotenv files
7 | .env
8 |
9 | # User-specific files
10 | *.rsuser
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # User-specific files (MonoDevelop/Xamarin Studio)
17 | *.userprefs
18 |
19 | # Mono auto generated files
20 | mono_crash.*
21 |
22 | # Build results
23 | [Dd]ebug/
24 | [Dd]ebugPublic/
25 | [Rr]elease/
26 | [Rr]eleases/
27 | x64/
28 | x86/
29 | [Ww][Ii][Nn]32/
30 | [Aa][Rr][Mm]/
31 | [Aa][Rr][Mm]64/
32 | bld/
33 | [Bb]in/
34 | [Oo]bj/
35 | [Ll]og/
36 | [Ll]ogs/
37 |
38 | # Visual Studio 2015/2017 cache/options directory
39 | .vs/
40 | # Uncomment if you have tasks that create the project's static files in wwwroot
41 | #wwwroot/
42 |
43 | # Visual Studio 2017 auto generated files
44 | Generated\ Files/
45 |
46 | # MSTest test Results
47 | [Tt]est[Rr]esult*/
48 | [Bb]uild[Ll]og.*
49 |
50 | # NUnit
51 | *.VisualState.xml
52 | TestResult.xml
53 | nunit-*.xml
54 |
55 | # Build Results of an ATL Project
56 | [Dd]ebugPS/
57 | [Rr]eleasePS/
58 | dlldata.c
59 |
60 | # Benchmark Results
61 | BenchmarkDotNet.Artifacts/
62 |
63 | # .NET
64 | project.lock.json
65 | project.fragment.lock.json
66 | artifacts/
67 |
68 | # Tye
69 | .tye/
70 |
71 | # ASP.NET Scaffolding
72 | ScaffoldingReadMe.txt
73 |
74 | # StyleCop
75 | StyleCopReport.xml
76 |
77 | # Files built by Visual Studio
78 | *_i.c
79 | *_p.c
80 | *_h.h
81 | *.ilk
82 | *.meta
83 | *.obj
84 | *.iobj
85 | *.pch
86 | *.pdb
87 | *.ipdb
88 | *.pgc
89 | *.pgd
90 | *.rsp
91 | *.sbr
92 | *.tlb
93 | *.tli
94 | *.tlh
95 | *.tmp
96 | *.tmp_proj
97 | *_wpftmp.csproj
98 | *.log
99 | *.tlog
100 | *.vspscc
101 | *.vssscc
102 | .builds
103 | *.pidb
104 | *.svclog
105 | *.scc
106 |
107 | # Chutzpah Test files
108 | _Chutzpah*
109 |
110 | # Visual C++ cache files
111 | ipch/
112 | *.aps
113 | *.ncb
114 | *.opendb
115 | *.opensdf
116 | *.sdf
117 | *.cachefile
118 | *.VC.db
119 | *.VC.VC.opendb
120 |
121 | # Visual Studio profiler
122 | *.psess
123 | *.vsp
124 | *.vspx
125 | *.sap
126 |
127 | # Visual Studio Trace Files
128 | *.e2e
129 |
130 | # TFS 2012 Local Workspace
131 | $tf/
132 |
133 | # Guidance Automation Toolkit
134 | *.gpState
135 |
136 | # ReSharper is a .NET coding add-in
137 | _ReSharper*/
138 | *.[Rr]e[Ss]harper
139 | *.DotSettings.user
140 |
141 | # TeamCity is a build add-in
142 | _TeamCity*
143 |
144 | # DotCover is a Code Coverage Tool
145 | *.dotCover
146 |
147 | # AxoCover is a Code Coverage Tool
148 | .axoCover/*
149 | !.axoCover/settings.json
150 |
151 | # Coverlet is a free, cross platform Code Coverage Tool
152 | coverage*.json
153 | coverage*.xml
154 | coverage*.info
155 |
156 | # Visual Studio code coverage results
157 | *.coverage
158 | *.coveragexml
159 |
160 | # NCrunch
161 | _NCrunch_*
162 | .*crunch*.local.xml
163 | nCrunchTemp_*
164 |
165 | # MightyMoose
166 | *.mm.*
167 | AutoTest.Net/
168 |
169 | # Web workbench (sass)
170 | .sass-cache/
171 |
172 | # Installshield output folder
173 | [Ee]xpress/
174 |
175 | # DocProject is a documentation generator add-in
176 | DocProject/buildhelp/
177 | DocProject/Help/*.HxT
178 | DocProject/Help/*.HxC
179 | DocProject/Help/*.hhc
180 | DocProject/Help/*.hhk
181 | DocProject/Help/*.hhp
182 | DocProject/Help/Html2
183 | DocProject/Help/html
184 |
185 | # Click-Once directory
186 | publish/
187 |
188 | # Publish Web Output
189 | *.[Pp]ublish.xml
190 | *.azurePubxml
191 | # Note: Comment the next line if you want to checkin your web deploy settings,
192 | # but database connection strings (with potential passwords) will be unencrypted
193 | *.pubxml
194 | *.publishproj
195 |
196 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
197 | # checkin your Azure Web App publish settings, but sensitive information contained
198 | # in these scripts will be unencrypted
199 | PublishScripts/
200 |
201 | # NuGet Packages
202 | *.nupkg
203 | # NuGet Symbol Packages
204 | *.snupkg
205 | # The packages folder can be ignored because of Package Restore
206 | **/[Pp]ackages/*
207 | # except build/, which is used as an MSBuild target.
208 | !**/[Pp]ackages/build/
209 | # Uncomment if necessary however generally it will be regenerated when needed
210 | #!**/[Pp]ackages/repositories.config
211 | # NuGet v3's project.json files produces more ignorable files
212 | *.nuget.props
213 | *.nuget.targets
214 |
215 | # Microsoft Azure Build Output
216 | csx/
217 | *.build.csdef
218 |
219 | # Microsoft Azure Emulator
220 | ecf/
221 | rcf/
222 |
223 | # Windows Store app package directories and files
224 | AppPackages/
225 | BundleArtifacts/
226 | Package.StoreAssociation.xml
227 | _pkginfo.txt
228 | *.appx
229 | *.appxbundle
230 | *.appxupload
231 |
232 | # Visual Studio cache files
233 | # files ending in .cache can be ignored
234 | *.[Cc]ache
235 | # but keep track of directories ending in .cache
236 | !?*.[Cc]ache/
237 |
238 | # Others
239 | ClientBin/
240 | ~$*
241 | *~
242 | *.dbmdl
243 | *.dbproj.schemaview
244 | *.jfm
245 | *.pfx
246 | *.publishsettings
247 | orleans.codegen.cs
248 |
249 | # Including strong name files can present a security risk
250 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
251 | #*.snk
252 |
253 | # Since there are multiple workflows, uncomment next line to ignore bower_components
254 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
255 | #bower_components/
256 |
257 | # RIA/Silverlight projects
258 | Generated_Code/
259 |
260 | # Backup & report files from converting an old project file
261 | # to a newer Visual Studio version. Backup files are not needed,
262 | # because we have git ;-)
263 | _UpgradeReport_Files/
264 | Backup*/
265 | UpgradeLog*.XML
266 | UpgradeLog*.htm
267 | ServiceFabricBackup/
268 | *.rptproj.bak
269 |
270 | # SQL Server files
271 | *.mdf
272 | *.ldf
273 | *.ndf
274 |
275 | # Business Intelligence projects
276 | *.rdl.data
277 | *.bim.layout
278 | *.bim_*.settings
279 | *.rptproj.rsuser
280 | *- [Bb]ackup.rdl
281 | *- [Bb]ackup ([0-9]).rdl
282 | *- [Bb]ackup ([0-9][0-9]).rdl
283 |
284 | # Microsoft Fakes
285 | FakesAssemblies/
286 |
287 | # GhostDoc plugin setting file
288 | *.GhostDoc.xml
289 |
290 | # Node.js Tools for Visual Studio
291 | .ntvs_analysis.dat
292 | node_modules/
293 |
294 | # Visual Studio 6 build log
295 | *.plg
296 |
297 | # Visual Studio 6 workspace options file
298 | *.opt
299 |
300 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
301 | *.vbw
302 |
303 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
304 | *.vbp
305 |
306 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
307 | *.dsw
308 | *.dsp
309 |
310 | # Visual Studio 6 technical files
311 | *.ncb
312 | *.aps
313 |
314 | # Visual Studio LightSwitch build output
315 | **/*.HTMLClient/GeneratedArtifacts
316 | **/*.DesktopClient/GeneratedArtifacts
317 | **/*.DesktopClient/ModelManifest.xml
318 | **/*.Server/GeneratedArtifacts
319 | **/*.Server/ModelManifest.xml
320 | _Pvt_Extensions
321 |
322 | # Paket dependency manager
323 | .paket/paket.exe
324 | paket-files/
325 |
326 | # FAKE - F# Make
327 | .fake/
328 |
329 | # CodeRush personal settings
330 | .cr/personal
331 |
332 | # Python Tools for Visual Studio (PTVS)
333 | __pycache__/
334 | *.pyc
335 |
336 | # Cake - Uncomment if you are using it
337 | # tools/**
338 | # !tools/packages.config
339 |
340 | # Tabs Studio
341 | *.tss
342 |
343 | # Telerik's JustMock configuration file
344 | *.jmconfig
345 |
346 | # BizTalk build output
347 | *.btp.cs
348 | *.btm.cs
349 | *.odx.cs
350 | *.xsd.cs
351 |
352 | # OpenCover UI analysis results
353 | OpenCover/
354 |
355 | # Azure Stream Analytics local run output
356 | ASALocalRun/
357 |
358 | # MSBuild Binary and Structured Log
359 | *.binlog
360 |
361 | # NVidia Nsight GPU debugger configuration file
362 | *.nvuser
363 |
364 | # MFractors (Xamarin productivity tool) working folder
365 | .mfractor/
366 |
367 | # Local History for Visual Studio
368 | .localhistory/
369 |
370 | # Visual Studio History (VSHistory) files
371 | .vshistory/
372 |
373 | # BeatPulse healthcheck temp database
374 | healthchecksdb
375 |
376 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
377 | MigrationBackup/
378 |
379 | # Ionide (cross platform F# VS Code tools) working folder
380 | .ionide/
381 |
382 | # Fody - auto-generated XML schema
383 | FodyWeavers.xsd
384 |
385 | # VS Code files for those working on multiple tools
386 | .vscode/*
387 | !.vscode/settings.json
388 | !.vscode/tasks.json
389 | !.vscode/launch.json
390 | !.vscode/extensions.json
391 | *.code-workspace
392 |
393 | # Local History for Visual Studio Code
394 | .history/
395 |
396 | # Windows Installer files from build outputs
397 | *.cab
398 | *.msi
399 | *.msix
400 | *.msm
401 | *.msp
402 |
403 | # JetBrains Rider
404 | *.sln.iml
405 | .idea/
406 |
407 | ##
408 | ## Visual studio for Mac
409 | ##
410 |
411 |
412 | # globs
413 | Makefile.in
414 | *.userprefs
415 | *.usertasks
416 | config.make
417 | config.status
418 | aclocal.m4
419 | install-sh
420 | autom4te.cache/
421 | *.tar.gz
422 | tarballs/
423 | test-results/
424 |
425 | # Mac bundle stuff
426 | *.dmg
427 | *.app
428 |
429 | # content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
430 | # General
431 | .DS_Store
432 | .AppleDouble
433 | .LSOverride
434 |
435 | # Icon must end with two \r
436 | Icon
437 |
438 |
439 | # Thumbnails
440 | ._*
441 |
442 | # Files that might appear in the root of a volume
443 | .DocumentRevisions-V100
444 | .fseventsd
445 | .Spotlight-V100
446 | .TemporaryItems
447 | .Trashes
448 | .VolumeIcon.icns
449 | .com.apple.timemachine.donotpresent
450 |
451 | # Directories potentially created on remote AFP share
452 | .AppleDB
453 | .AppleDesktop
454 | Network Trash Folder
455 | Temporary Items
456 | .apdisk
457 |
458 | # content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
459 | # Windows thumbnail cache files
460 | Thumbs.db
461 | ehthumbs.db
462 | ehthumbs_vista.db
463 |
464 | # Dump file
465 | *.stackdump
466 |
467 | # Folder config file
468 | [Dd]esktop.ini
469 |
470 | # Recycle Bin used on file shares
471 | $RECYCLE.BIN/
472 |
473 | # Windows Installer files
474 | *.cab
475 | *.msi
476 | *.msix
477 | *.msm
478 | *.msp
479 |
480 | # Windows shortcuts
481 | *.lnk
482 |
483 | # Vim temporary swap files
484 | *.swp
485 |
--------------------------------------------------------------------------------
/src/GemiNet.Extensions.AI/GemiNet.Extensions.AI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0;net8.0;netstandard2.1
5 | 13
6 | enable
7 | enable
8 |
9 |
10 | ai;gemini;
11 | GemiNet Microsoft.Extensions.AI extensions
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/GemiNet.Extensions.AI/GoogleGenAIChatClient.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using Microsoft.Extensions.AI;
3 |
4 | namespace GemiNet.Extensions.AI;
5 |
6 | internal sealed class GoogleGenAIChatClient(GoogleGenAI ai, string model, IServiceProvider? serviceProvider = null) : IChatClient
7 | {
8 | public object? GetService(Type serviceType, object? serviceKey = null)
9 | {
10 | return serviceProvider?.GetService(serviceType);
11 | }
12 |
13 | public async Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
14 | {
15 | var response = await ai.Models.GenerateContentAsync(
16 | CreateGenerateContentRequest(messages, options),
17 | cancellationToken);
18 |
19 | return ModelConverter.CreateChatResponse(response, DateTime.Now);
20 | }
21 |
22 | public async IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
23 | {
24 | var request = CreateGenerateContentRequest(messages, options);
25 |
26 | await foreach (var response in ai.Models.GenerateContentStreamAsync(request, cancellationToken))
27 | {
28 | if (response.Candidates == null || response.Candidates.Length == 0) continue;
29 |
30 | var candidate = response.Candidates?[0];
31 | if (candidate == null || candidate.Content == null) continue;
32 |
33 | yield return new ChatResponseUpdate
34 | {
35 | AuthorName = null,
36 | Role = ModelConverter.CreateChatRole(candidate.Content.Role),
37 | Contents = [.. candidate.Content.Parts.Select(ModelConverter.CreateAIContent)],
38 | RawRepresentation = response,
39 | CreatedAt = DateTime.Now,
40 | FinishReason = candidate.FinishReason switch
41 | {
42 | FinishReason.Stop => ChatFinishReason.Stop,
43 | FinishReason.MaxTokens => ChatFinishReason.Length,
44 | FinishReason.Safety => ChatFinishReason.ContentFilter,
45 | _ => null
46 | },
47 | ModelId = response.ModelVersion,
48 | };
49 | }
50 | }
51 |
52 | public void Dispose()
53 | {
54 | ai.Dispose();
55 | }
56 |
57 | GenerateContentRequest CreateGenerateContentRequest(IEnumerable messages, ChatOptions? options)
58 | {
59 | Content? systemInstruction = null;
60 | Contents contents = [];
61 |
62 | foreach (var message in messages)
63 | {
64 | if (message.Role == ChatRole.System)
65 | {
66 | if (systemInstruction is not null)
67 | {
68 | throw new GemiNetException("Cannot use multiple system instructions");
69 | }
70 |
71 | systemInstruction = ModelConverter.CreateContent(message);
72 | continue;
73 | }
74 |
75 | contents.Add(ModelConverter.CreateContent(message));
76 | }
77 |
78 | return new()
79 | {
80 | Model = options?.ModelId ?? model,
81 | SystemInstruction = systemInstruction,
82 | Tools = ModelConverter.CreateTools(options?.Tools),
83 | Contents = contents,
84 | GenerationConfig = ModelConverter.CreateGenerateConfig(options),
85 | };
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/src/GemiNet.Extensions.AI/GoogleGenAIEmbeddingGenerator.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.AI;
2 |
3 | namespace GemiNet.Extensions.AI;
4 |
5 | internal sealed class GoogleGenAIEmbeddingGenerator(GoogleGenAI ai, string model) : IEmbeddingGenerator>
6 | {
7 | const int GeminiEmbeddingSize = 768;
8 |
9 | public async Task>> GenerateAsync(
10 | IEnumerable values,
11 | EmbeddingGenerationOptions? options = null,
12 | CancellationToken cancellationToken = default)
13 | {
14 | var request = new EmbedContentRequest
15 | {
16 | Model = model,
17 | Contents = new Content
18 | {
19 | Parts = [.. values.Select(v => new Part { Text = v })]
20 | },
21 | OutputDimensionality = options?.Dimensions,
22 | };
23 |
24 | var response = await ai.Models.EmbedContentAsync(request, cancellationToken);
25 |
26 | var embeddingSize = options?.Dimensions ?? GeminiEmbeddingSize;
27 |
28 | var vector = response.Embeddings
29 | .SelectMany(x => x.Values)
30 | .Select(x => (float)x)
31 | .ToArray();
32 |
33 | if (vector.Length % embeddingSize != 0)
34 | {
35 | throw new InvalidOperationException($"The returned embedding vector's size is not a multiple of the expected dimensions '{embeddingSize}'.");
36 | }
37 |
38 | var generatedEmbeddings = new GeneratedEmbeddings>(vector.Length / embeddingSize);
39 |
40 | for (int i = 0; i < vector.Length; i += embeddingSize)
41 | {
42 | var embedding = new Embedding(vector.AsMemory().Slice(i, embeddingSize));
43 | generatedEmbeddings.Add(embedding);
44 | }
45 |
46 | return generatedEmbeddings;
47 | }
48 |
49 | public object? GetService(Type serviceType, object? serviceKey = null)
50 | {
51 | if (serviceType == typeof(GoogleGenAI))
52 | {
53 | return ai;
54 | }
55 |
56 | return null;
57 | }
58 |
59 | public void Dispose()
60 | {
61 | }
62 | }
--------------------------------------------------------------------------------
/src/GemiNet.Extensions.AI/GoogleGenAIExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.AI;
2 |
3 | namespace GemiNet.Extensions.AI;
4 |
5 | public static class GoogleGenAIExtensions
6 | {
7 | public static IChatClient AsChatClient(this GoogleGenAI ai, string model)
8 | {
9 | return new GoogleGenAIChatClient(ai, model);
10 | }
11 |
12 | public static IEmbeddingGenerator> AsEmbeddingGenerator(this GoogleGenAI ai, string model)
13 | {
14 | return new GoogleGenAIEmbeddingGenerator(ai, model);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/GemiNet.Extensions.AI/ModelConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using Microsoft.Extensions.AI;
3 |
4 | namespace GemiNet.Extensions.AI;
5 |
6 | internal static class ModelConverter
7 | {
8 | public static string? CreateRole(ChatRole role)
9 | {
10 | if (role == ChatRole.System) return null;
11 | if (role == ChatRole.User) return "user";
12 | if (role == ChatRole.Assistant) return "model";
13 | if (role == ChatRole.Tool) return "model";
14 |
15 | throw new GemiNetException($"Unsupported role: '{role}'");
16 | }
17 |
18 | public static ChatRole CreateChatRole(string? role)
19 | {
20 | if (role is null)
21 | {
22 | return ChatRole.System;
23 | }
24 |
25 | if (string.Equals(role, "user", StringComparison.OrdinalIgnoreCase))
26 | {
27 | return ChatRole.User;
28 | }
29 |
30 | if (string.Equals(role, "model", StringComparison.OrdinalIgnoreCase))
31 | {
32 | return ChatRole.Assistant;
33 | }
34 |
35 | throw new GemiNetException($"Unsupported role: {role}");
36 | }
37 |
38 | public static Content CreateContent(ChatMessage chatMessage)
39 | {
40 | return new Content
41 | {
42 | Role = CreateRole(chatMessage.Role),
43 | Parts = [.. chatMessage.Contents.Select(CreatePart)],
44 | };
45 | }
46 |
47 | public static Part CreatePart(AIContent content)
48 | {
49 | return content switch
50 | {
51 | TextContent textContent => new Part
52 | {
53 | Text = textContent.Text
54 | },
55 | DataContent dataContent => new Part
56 | {
57 | InlineData = new Blob
58 | {
59 | Data = Convert.ToBase64String(dataContent.Data.Span),
60 | MimeType = dataContent.MediaType,
61 | }
62 | },
63 | UriContent uriContent => new Part
64 | {
65 | FileData = new FileData
66 | {
67 | FileUri = uriContent.Uri.AbsoluteUri,
68 | MimeType = uriContent.MediaType,
69 | }
70 | },
71 | FunctionCallContent functionCall => new Part
72 | {
73 | FunctionCall = new FunctionCall
74 | {
75 | Id = functionCall.CallId,
76 | Name = functionCall.Name,
77 | Args = JsonSerializer.SerializeToElement(functionCall.Arguments),
78 | }
79 | },
80 | FunctionResultContent functionResult => new Part
81 | {
82 | FunctionResponse = new FunctionResponse
83 | {
84 | Id = functionResult.CallId,
85 | Name = functionResult.CallId,
86 | Response = JsonSerializer.SerializeToElement(new
87 | {
88 | content = JsonSerializer.SerializeToElement(functionResult.Result),
89 | }),
90 | }
91 | },
92 | _ => throw new GemiNetException($"Unsupprted AIContent type: {content.GetType()}")
93 | };
94 | }
95 |
96 | public static AIContent CreateAIContent(Part part)
97 | {
98 | if (part.Text is not null)
99 | {
100 | if (part.Thought == true)
101 | {
102 | return new TextReasoningContent(part.Text)
103 | {
104 | RawRepresentation = part.Text,
105 | };
106 | }
107 |
108 | return new TextContent(part.Text)
109 | {
110 | RawRepresentation = part.Text,
111 | };
112 | }
113 |
114 | if (part.InlineData is not null)
115 | {
116 | return new DataContent(part.InlineData.Data, part.InlineData.MimeType)
117 | {
118 | RawRepresentation = part.InlineData,
119 | };
120 | }
121 |
122 | if (part.FunctionCall is not null)
123 | {
124 | var callId = part.FunctionCall.Id ?? $"{part.FunctionCall.Name}-{Guid.NewGuid()}";
125 |
126 | return new FunctionCallContent(callId, part.FunctionCall.Name,
127 | JsonSerializer.Deserialize>(part.FunctionCall.Args.GetValueOrDefault()))
128 | {
129 | RawRepresentation = part.FunctionCall,
130 | };
131 | }
132 |
133 | if (part.FunctionResponse is not null)
134 | {
135 | var responseId = part.FunctionResponse.Id ?? $"{part.FunctionResponse.Name}-{Guid.NewGuid()}";
136 |
137 | return new FunctionResultContent(responseId, part.FunctionResponse.Response)
138 | {
139 | RawRepresentation = part.FunctionResponse,
140 | };
141 | }
142 |
143 | if (part.FileData is not null)
144 | {
145 | return new DataContent(part.FileData.FileUri, part.FileData.MimeType)
146 | {
147 | RawRepresentation = part.FileData,
148 | };
149 | }
150 |
151 | if (part.ExecutableCode is not null)
152 | {
153 | return new AIContent
154 | {
155 | RawRepresentation = part.ExecutableCode,
156 | };
157 | }
158 |
159 | if (part.CodeExecutionResult is not null)
160 | {
161 | return new AIContent
162 | {
163 | RawRepresentation = part.CodeExecutionResult,
164 | };
165 | }
166 |
167 | throw new GemiNetException($"All properties of Part are null.");
168 | }
169 |
170 | public static GenerationConfig? CreateGenerateConfig(ChatOptions? options)
171 | {
172 | if (options is null) return null;
173 |
174 | ThinkingConfig? thinkingConfig = null;
175 |
176 | if (options.AdditionalProperties?.TryGetValue("thinkingConfig", out var thinkingConfigObj) is true
177 | && thinkingConfigObj is ThinkingConfig obj)
178 | {
179 | thinkingConfig = obj;
180 | }
181 |
182 | var configuration = new GenerationConfig
183 | {
184 | StopSequences = options.StopSequences?.ToArray(),
185 | ResponseMimeType = options.ResponseFormat is ChatResponseFormatJson
186 | ? "application/json"
187 | : null,
188 | ResponseSchema = CreateSchema(options.ResponseFormat),
189 | MaxOutputTokens = options.MaxOutputTokens,
190 | Temperature = options.Temperature,
191 | TopP = options.TopP,
192 | TopK = options.TopK,
193 | Seed = options.Seed == null ? null : (int)options.Seed.Value,
194 | PresencePenalty = options.PresencePenalty,
195 | FrequencyPenalty = options.FrequencyPenalty,
196 | ThinkingConfig = thinkingConfig
197 | };
198 |
199 | return configuration;
200 | }
201 |
202 | public static Tool[]? CreateTools(IList? tools)
203 | {
204 | if (tools is null) return null;
205 |
206 | var toolList = new List(tools.Count);
207 | List? functionDeclarations = null;
208 |
209 | foreach (var tool in tools)
210 | {
211 | if (tool is HostedCodeInterpreterTool)
212 | {
213 | toolList.Add(new Tool
214 | {
215 | CodeExecution = new CodeExecution()
216 | });
217 | continue;
218 | }
219 |
220 | if (tool is AIFunction function)
221 | {
222 | functionDeclarations ??= [];
223 |
224 | functionDeclarations.Add(new FunctionDeclaration
225 | {
226 | Name = function.Name,
227 | Description = function.Description,
228 | Parameters = CreateFunctionParameters(function.JsonSchema),
229 | Response = null,
230 | });
231 |
232 | continue;
233 | }
234 |
235 | throw new GemiNetException($"Unsupported tool type: {tool.GetType()}");
236 | }
237 |
238 | if (functionDeclarations is not null)
239 | {
240 | toolList.Add(new Tool
241 | {
242 | FunctionDeclarations = functionDeclarations.ToArray()
243 | });
244 | }
245 |
246 | return toolList.ToArray();
247 | }
248 |
249 | public static Schema? CreateSchema(ChatResponseFormat? responseFormat)
250 | {
251 | if (responseFormat is null) return null;
252 |
253 | if (responseFormat is ChatResponseFormatJson { Schema: JsonElement schema })
254 | {
255 | return Schema.FromJsonElement(schema);
256 | }
257 |
258 | if (responseFormat is ChatResponseFormatText) return null;
259 |
260 | throw new GemiNetException($"Unsupported response format: '{responseFormat}'");
261 | }
262 |
263 | public static Schema? CreateFunctionParameters(JsonElement functionSchema)
264 | {
265 | var properties = functionSchema.GetProperty("properties");
266 |
267 | if (properties.ValueKind != JsonValueKind.Object)
268 | {
269 | throw new GemiNetException($"Expected object but got {properties.ValueKind}");
270 | }
271 |
272 | Dictionary? parameters = null;
273 |
274 | foreach (var param in properties.EnumerateObject())
275 | {
276 | parameters ??= [];
277 | parameters[param.Name] = Schema.FromJsonElement(param.Value);
278 | }
279 |
280 | if (parameters is null) return null;
281 |
282 | return new Schema
283 | {
284 | Type = DataType.Unspecified,
285 | Properties = parameters,
286 | };
287 | }
288 |
289 | public static ChatResponse CreateChatResponse(GenerateContentResponse response, DateTimeOffset createdAt)
290 | {
291 | var choices = response.Candidates?
292 | .Select(x => new ChatMessage
293 | {
294 | AuthorName = null,
295 | Role = CreateChatRole(x.Content?.Role),
296 | Contents = [.. x.Content?.Parts.Select(CreateAIContent) ?? []],
297 | RawRepresentation = x,
298 | AdditionalProperties = null
299 | })
300 | .ToArray();
301 |
302 | return new ChatResponse(choices)
303 | {
304 | ResponseId = null,
305 | ConversationId = null,
306 | ModelId = response.ModelVersion,
307 | CreatedAt = createdAt,
308 | FinishReason = response.Candidates?[0].FinishReason switch
309 | {
310 | FinishReason.Stop => ChatFinishReason.Stop,
311 | FinishReason.MaxTokens => ChatFinishReason.Length,
312 | FinishReason.Safety => ChatFinishReason.ContentFilter,
313 | _ => null
314 | },
315 | Usage = new UsageDetails
316 | {
317 | InputTokenCount = response.UsageMetadata?.PromptTokenCount,
318 | OutputTokenCount = response.UsageMetadata?.CandidatesTokenCount,
319 | TotalTokenCount = response.UsageMetadata?.TotalTokenCount,
320 | AdditionalCounts = null,
321 | },
322 | RawRepresentation = response,
323 | AdditionalProperties = null
324 | };
325 | }
326 | }
--------------------------------------------------------------------------------
/src/GemiNet/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from `dotnet new gitignore`
5 |
6 | # dotenv files
7 | .env
8 |
9 | # User-specific files
10 | *.rsuser
11 | *.suo
12 | *.user
13 | *.userosscache
14 | *.sln.docstates
15 |
16 | # User-specific files (MonoDevelop/Xamarin Studio)
17 | *.userprefs
18 |
19 | # Mono auto generated files
20 | mono_crash.*
21 |
22 | # Build results
23 | [Dd]ebug/
24 | [Dd]ebugPublic/
25 | [Rr]elease/
26 | [Rr]eleases/
27 | x64/
28 | x86/
29 | [Ww][Ii][Nn]32/
30 | [Aa][Rr][Mm]/
31 | [Aa][Rr][Mm]64/
32 | bld/
33 | [Bb]in/
34 | [Oo]bj/
35 | [Ll]og/
36 | [Ll]ogs/
37 |
38 | # Visual Studio 2015/2017 cache/options directory
39 | .vs/
40 | # Uncomment if you have tasks that create the project's static files in wwwroot
41 | #wwwroot/
42 |
43 | # Visual Studio 2017 auto generated files
44 | Generated\ Files/
45 |
46 | # MSTest test Results
47 | [Tt]est[Rr]esult*/
48 | [Bb]uild[Ll]og.*
49 |
50 | # NUnit
51 | *.VisualState.xml
52 | TestResult.xml
53 | nunit-*.xml
54 |
55 | # Build Results of an ATL Project
56 | [Dd]ebugPS/
57 | [Rr]eleasePS/
58 | dlldata.c
59 |
60 | # Benchmark Results
61 | BenchmarkDotNet.Artifacts/
62 |
63 | # .NET
64 | project.lock.json
65 | project.fragment.lock.json
66 | artifacts/
67 |
68 | # Tye
69 | .tye/
70 |
71 | # ASP.NET Scaffolding
72 | ScaffoldingReadMe.txt
73 |
74 | # StyleCop
75 | StyleCopReport.xml
76 |
77 | # Files built by Visual Studio
78 | *_i.c
79 | *_p.c
80 | *_h.h
81 | *.ilk
82 | *.meta
83 | *.obj
84 | *.iobj
85 | *.pch
86 | *.pdb
87 | *.ipdb
88 | *.pgc
89 | *.pgd
90 | *.rsp
91 | *.sbr
92 | *.tlb
93 | *.tli
94 | *.tlh
95 | *.tmp
96 | *.tmp_proj
97 | *_wpftmp.csproj
98 | *.log
99 | *.tlog
100 | *.vspscc
101 | *.vssscc
102 | .builds
103 | *.pidb
104 | *.svclog
105 | *.scc
106 |
107 | # Chutzpah Test files
108 | _Chutzpah*
109 |
110 | # Visual C++ cache files
111 | ipch/
112 | *.aps
113 | *.ncb
114 | *.opendb
115 | *.opensdf
116 | *.sdf
117 | *.cachefile
118 | *.VC.db
119 | *.VC.VC.opendb
120 |
121 | # Visual Studio profiler
122 | *.psess
123 | *.vsp
124 | *.vspx
125 | *.sap
126 |
127 | # Visual Studio Trace Files
128 | *.e2e
129 |
130 | # TFS 2012 Local Workspace
131 | $tf/
132 |
133 | # Guidance Automation Toolkit
134 | *.gpState
135 |
136 | # ReSharper is a .NET coding add-in
137 | _ReSharper*/
138 | *.[Rr]e[Ss]harper
139 | *.DotSettings.user
140 |
141 | # TeamCity is a build add-in
142 | _TeamCity*
143 |
144 | # DotCover is a Code Coverage Tool
145 | *.dotCover
146 |
147 | # AxoCover is a Code Coverage Tool
148 | .axoCover/*
149 | !.axoCover/settings.json
150 |
151 | # Coverlet is a free, cross platform Code Coverage Tool
152 | coverage*.json
153 | coverage*.xml
154 | coverage*.info
155 |
156 | # Visual Studio code coverage results
157 | *.coverage
158 | *.coveragexml
159 |
160 | # NCrunch
161 | _NCrunch_*
162 | .*crunch*.local.xml
163 | nCrunchTemp_*
164 |
165 | # MightyMoose
166 | *.mm.*
167 | AutoTest.Net/
168 |
169 | # Web workbench (sass)
170 | .sass-cache/
171 |
172 | # Installshield output folder
173 | [Ee]xpress/
174 |
175 | # DocProject is a documentation generator add-in
176 | DocProject/buildhelp/
177 | DocProject/Help/*.HxT
178 | DocProject/Help/*.HxC
179 | DocProject/Help/*.hhc
180 | DocProject/Help/*.hhk
181 | DocProject/Help/*.hhp
182 | DocProject/Help/Html2
183 | DocProject/Help/html
184 |
185 | # Click-Once directory
186 | publish/
187 |
188 | # Publish Web Output
189 | *.[Pp]ublish.xml
190 | *.azurePubxml
191 | # Note: Comment the next line if you want to checkin your web deploy settings,
192 | # but database connection strings (with potential passwords) will be unencrypted
193 | *.pubxml
194 | *.publishproj
195 |
196 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
197 | # checkin your Azure Web App publish settings, but sensitive information contained
198 | # in these scripts will be unencrypted
199 | PublishScripts/
200 |
201 | # NuGet Packages
202 | *.nupkg
203 | # NuGet Symbol Packages
204 | *.snupkg
205 | # The packages folder can be ignored because of Package Restore
206 | **/[Pp]ackages/*
207 | # except build/, which is used as an MSBuild target.
208 | !**/[Pp]ackages/build/
209 | # Uncomment if necessary however generally it will be regenerated when needed
210 | #!**/[Pp]ackages/repositories.config
211 | # NuGet v3's project.json files produces more ignorable files
212 | *.nuget.props
213 | *.nuget.targets
214 |
215 | # Microsoft Azure Build Output
216 | csx/
217 | *.build.csdef
218 |
219 | # Microsoft Azure Emulator
220 | ecf/
221 | rcf/
222 |
223 | # Windows Store app package directories and files
224 | AppPackages/
225 | BundleArtifacts/
226 | Package.StoreAssociation.xml
227 | _pkginfo.txt
228 | *.appx
229 | *.appxbundle
230 | *.appxupload
231 |
232 | # Visual Studio cache files
233 | # files ending in .cache can be ignored
234 | *.[Cc]ache
235 | # but keep track of directories ending in .cache
236 | !?*.[Cc]ache/
237 |
238 | # Others
239 | ClientBin/
240 | ~$*
241 | *~
242 | *.dbmdl
243 | *.dbproj.schemaview
244 | *.jfm
245 | *.pfx
246 | *.publishsettings
247 | orleans.codegen.cs
248 |
249 | # Including strong name files can present a security risk
250 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
251 | #*.snk
252 |
253 | # Since there are multiple workflows, uncomment next line to ignore bower_components
254 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
255 | #bower_components/
256 |
257 | # RIA/Silverlight projects
258 | Generated_Code/
259 |
260 | # Backup & report files from converting an old project file
261 | # to a newer Visual Studio version. Backup files are not needed,
262 | # because we have git ;-)
263 | _UpgradeReport_Files/
264 | Backup*/
265 | UpgradeLog*.XML
266 | UpgradeLog*.htm
267 | ServiceFabricBackup/
268 | *.rptproj.bak
269 |
270 | # SQL Server files
271 | *.mdf
272 | *.ldf
273 | *.ndf
274 |
275 | # Business Intelligence projects
276 | *.rdl.data
277 | *.bim.layout
278 | *.bim_*.settings
279 | *.rptproj.rsuser
280 | *- [Bb]ackup.rdl
281 | *- [Bb]ackup ([0-9]).rdl
282 | *- [Bb]ackup ([0-9][0-9]).rdl
283 |
284 | # Microsoft Fakes
285 | FakesAssemblies/
286 |
287 | # GhostDoc plugin setting file
288 | *.GhostDoc.xml
289 |
290 | # Node.js Tools for Visual Studio
291 | .ntvs_analysis.dat
292 | node_modules/
293 |
294 | # Visual Studio 6 build log
295 | *.plg
296 |
297 | # Visual Studio 6 workspace options file
298 | *.opt
299 |
300 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
301 | *.vbw
302 |
303 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
304 | *.vbp
305 |
306 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
307 | *.dsw
308 | *.dsp
309 |
310 | # Visual Studio 6 technical files
311 | *.ncb
312 | *.aps
313 |
314 | # Visual Studio LightSwitch build output
315 | **/*.HTMLClient/GeneratedArtifacts
316 | **/*.DesktopClient/GeneratedArtifacts
317 | **/*.DesktopClient/ModelManifest.xml
318 | **/*.Server/GeneratedArtifacts
319 | **/*.Server/ModelManifest.xml
320 | _Pvt_Extensions
321 |
322 | # Paket dependency manager
323 | .paket/paket.exe
324 | paket-files/
325 |
326 | # FAKE - F# Make
327 | .fake/
328 |
329 | # CodeRush personal settings
330 | .cr/personal
331 |
332 | # Python Tools for Visual Studio (PTVS)
333 | __pycache__/
334 | *.pyc
335 |
336 | # Cake - Uncomment if you are using it
337 | # tools/**
338 | # !tools/packages.config
339 |
340 | # Tabs Studio
341 | *.tss
342 |
343 | # Telerik's JustMock configuration file
344 | *.jmconfig
345 |
346 | # BizTalk build output
347 | *.btp.cs
348 | *.btm.cs
349 | *.odx.cs
350 | *.xsd.cs
351 |
352 | # OpenCover UI analysis results
353 | OpenCover/
354 |
355 | # Azure Stream Analytics local run output
356 | ASALocalRun/
357 |
358 | # MSBuild Binary and Structured Log
359 | *.binlog
360 |
361 | # NVidia Nsight GPU debugger configuration file
362 | *.nvuser
363 |
364 | # MFractors (Xamarin productivity tool) working folder
365 | .mfractor/
366 |
367 | # Local History for Visual Studio
368 | .localhistory/
369 |
370 | # Visual Studio History (VSHistory) files
371 | .vshistory/
372 |
373 | # BeatPulse healthcheck temp database
374 | healthchecksdb
375 |
376 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
377 | MigrationBackup/
378 |
379 | # Ionide (cross platform F# VS Code tools) working folder
380 | .ionide/
381 |
382 | # Fody - auto-generated XML schema
383 | FodyWeavers.xsd
384 |
385 | # VS Code files for those working on multiple tools
386 | .vscode/*
387 | !.vscode/settings.json
388 | !.vscode/tasks.json
389 | !.vscode/launch.json
390 | !.vscode/extensions.json
391 | *.code-workspace
392 |
393 | # Local History for Visual Studio Code
394 | .history/
395 |
396 | # Windows Installer files from build outputs
397 | *.cab
398 | *.msi
399 | *.msix
400 | *.msm
401 | *.msp
402 |
403 | # JetBrains Rider
404 | *.sln.iml
405 | .idea/
406 |
407 | ##
408 | ## Visual studio for Mac
409 | ##
410 |
411 |
412 | # globs
413 | Makefile.in
414 | *.userprefs
415 | *.usertasks
416 | config.make
417 | config.status
418 | aclocal.m4
419 | install-sh
420 | autom4te.cache/
421 | *.tar.gz
422 | tarballs/
423 | test-results/
424 |
425 | # Mac bundle stuff
426 | *.dmg
427 | *.app
428 |
429 | # content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
430 | # General
431 | .DS_Store
432 | .AppleDouble
433 | .LSOverride
434 |
435 | # Icon must end with two \r
436 | Icon
437 |
438 |
439 | # Thumbnails
440 | ._*
441 |
442 | # Files that might appear in the root of a volume
443 | .DocumentRevisions-V100
444 | .fseventsd
445 | .Spotlight-V100
446 | .TemporaryItems
447 | .Trashes
448 | .VolumeIcon.icns
449 | .com.apple.timemachine.donotpresent
450 |
451 | # Directories potentially created on remote AFP share
452 | .AppleDB
453 | .AppleDesktop
454 | Network Trash Folder
455 | Temporary Items
456 | .apdisk
457 |
458 | # content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
459 | # Windows thumbnail cache files
460 | Thumbs.db
461 | ehthumbs.db
462 | ehthumbs_vista.db
463 |
464 | # Dump file
465 | *.stackdump
466 |
467 | # Folder config file
468 | [Dd]esktop.ini
469 |
470 | # Recycle Bin used on file shares
471 | $RECYCLE.BIN/
472 |
473 | # Windows Installer files
474 | *.cab
475 | *.msi
476 | *.msix
477 | *.msm
478 | *.msp
479 |
480 | # Windows shortcuts
481 | *.lnk
482 |
483 | # Vim temporary swap files
484 | *.swp
485 |
--------------------------------------------------------------------------------
/src/GemiNet/GemiNet.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0;net8.0;net6.0;netstandard2.1
5 | 13
6 | enable
7 | enable
8 |
9 |
10 | ai;gemini;
11 | Unofficial Gemini Developer API client for .NET and Unity
12 |
13 |
14 |
15 |
16 | all
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/GemiNet/GemiNetException.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace GemiNet;
4 |
5 | public class GemiNetException : Exception
6 | {
7 | public GemiNetException(string? message) : base(message)
8 | {
9 | }
10 |
11 | public GemiNetException(string? message, Exception innerException) : base(message, innerException)
12 | {
13 | }
14 | }
15 |
16 |
17 | public record ErrorResponse
18 | {
19 | [JsonPropertyName("error")]
20 | public required ErrorResponseData Error { get; init; }
21 | }
22 |
23 | public record ErrorResponseData
24 | {
25 | [JsonPropertyName("code")]
26 | public required int Code { get; init; }
27 |
28 | [JsonPropertyName("message")]
29 | public required string Message { get; init; }
30 |
31 | [JsonPropertyName("status")]
32 | public required string Status { get; init; }
33 | }
--------------------------------------------------------------------------------
/src/GemiNet/GemiNetJsonSerializerContext.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace GemiNet;
4 |
5 | [JsonSourceGenerationOptions(
6 | GenerationMode = JsonSourceGenerationMode.Default,
7 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
8 | WriteIndented = false,
9 | UseStringEnumConverter = true)]
10 | [JsonSerializable(typeof(ErrorResponse))]
11 | [JsonSerializable(typeof(ErrorResponseData))]
12 |
13 | // Models
14 | [JsonSerializable(typeof(Model))]
15 | [JsonSerializable(typeof(ListModelsRequest))]
16 | [JsonSerializable(typeof(ListModelsResponse))]
17 |
18 | // Generating content
19 | [JsonSerializable(typeof(GenerateContentRequest))]
20 | [JsonSerializable(typeof(GenerateContentResponse))]
21 | [JsonSerializable(typeof(GenerateContentResponsePromptFeedback))]
22 | [JsonSerializable(typeof(BlockReason))]
23 | [JsonSerializable(typeof(GenerateContentResponseUsageMetadata))]
24 | [JsonSerializable(typeof(Candidate))]
25 | [JsonSerializable(typeof(FinishReason))]
26 | [JsonSerializable(typeof(GroundingAttribution))]
27 | [JsonSerializable(typeof(AttributionSourceId))]
28 | [JsonSerializable(typeof(GroundingPassageId))]
29 | [JsonSerializable(typeof(SemanticRetrieverChunk))]
30 | [JsonSerializable(typeof(GroundingMetadata))]
31 | [JsonSerializable(typeof(SearchEntryPoint))]
32 | [JsonSerializable(typeof(GroundingChunk))]
33 | [JsonSerializable(typeof(Web))]
34 | [JsonSerializable(typeof(GroundingSupport))]
35 | [JsonSerializable(typeof(Segment))]
36 | [JsonSerializable(typeof(RetrievalMetadata))]
37 | [JsonSerializable(typeof(LogprobsResult))]
38 | [JsonSerializable(typeof(LogprobsResultTopCandidates))]
39 | [JsonSerializable(typeof(LogprobsResultCandidate))]
40 | [JsonSerializable(typeof(UrlRetrievalMetadata))]
41 | [JsonSerializable(typeof(UrlRetrievalContext))]
42 | [JsonSerializable(typeof(CitationMetadata))]
43 | [JsonSerializable(typeof(CitationSource))]
44 | [JsonSerializable(typeof(GenerationConfig))]
45 | [JsonSerializable(typeof(Modality))]
46 | [JsonSerializable(typeof(ModalityTokenCount))]
47 | [JsonSerializable(typeof(SpeechConfig))]
48 | [JsonSerializable(typeof(VoiceConfig))]
49 | [JsonSerializable(typeof(PrebuiltVoiceConfig))]
50 | [JsonSerializable(typeof(ThinkingConfig))]
51 | [JsonSerializable(typeof(MediaResolution))]
52 | [JsonSerializable(typeof(HarmCategory))]
53 | [JsonSerializable(typeof(SafetyRating))]
54 | [JsonSerializable(typeof(HarmProbability))]
55 | [JsonSerializable(typeof(SafetySetting))]
56 | [JsonSerializable(typeof(HarmBlockThreshold))]
57 |
58 | // Tokens
59 | [JsonSerializable(typeof(CountTokensRequest))]
60 | [JsonSerializable(typeof(CountTokensResponse))]
61 |
62 | // Files
63 | [JsonSerializable(typeof(UploadFileRequest))]
64 | [JsonSerializable(typeof(UploadFileResponse))]
65 | [JsonSerializable(typeof(GetUploadUrlRequest))]
66 | [JsonSerializable(typeof(GetFileRequest))]
67 | [JsonSerializable(typeof(ListFilesRequest))]
68 | [JsonSerializable(typeof(DeleteFileRequest))]
69 | [JsonSerializable(typeof(UploadFileResponse))]
70 | [JsonSerializable(typeof(ListFilesResponse))]
71 | [JsonSerializable(typeof(File))]
72 | [JsonSerializable(typeof(VideoFileMetadata))]
73 | [JsonSerializable(typeof(FileState))]
74 | [JsonSerializable(typeof(FileSource))]
75 | [JsonSerializable(typeof(FileStatus))]
76 |
77 | // Caching
78 | [JsonSerializable(typeof(ListCachedContentsResponse))]
79 | [JsonSerializable(typeof(PatchChachedContentConfig))]
80 | [JsonSerializable(typeof(CachedContent))]
81 | [JsonSerializable(typeof(Content))]
82 | [JsonSerializable(typeof(Contents))]
83 | [JsonSerializable(typeof(Part))]
84 | [JsonSerializable(typeof(Blob))]
85 | [JsonSerializable(typeof(FunctionCall))]
86 | [JsonSerializable(typeof(FunctionResponse))]
87 | [JsonSerializable(typeof(FileData))]
88 | [JsonSerializable(typeof(ExecutableCode))]
89 | [JsonSerializable(typeof(ExecutableCodeLanguage))]
90 | [JsonSerializable(typeof(CodeExecutionResult))]
91 | [JsonSerializable(typeof(CodeExecutionOutcome))]
92 | [JsonSerializable(typeof(Tool))]
93 | [JsonSerializable(typeof(FunctionDeclaration))]
94 | [JsonSerializable(typeof(Schema))]
95 | [JsonSerializable(typeof(DataType))]
96 | [JsonSerializable(typeof(GoogleSearchRetrieval))]
97 | [JsonSerializable(typeof(DynamicRetrievalConfig))]
98 | [JsonSerializable(typeof(DynamicRetrievalMode))]
99 | [JsonSerializable(typeof(CodeExecution))]
100 | [JsonSerializable(typeof(GoogleSearch))]
101 | [JsonSerializable(typeof(ToolConfig))]
102 | [JsonSerializable(typeof(FunctionCallingConfig))]
103 | [JsonSerializable(typeof(FunctionCallingMode))]
104 | [JsonSerializable(typeof(CachingUsageMetadata))]
105 |
106 | // Embbedings
107 | [JsonSerializable(typeof(EmbedContentRequestJsonData))]
108 | [JsonSerializable(typeof(BatchEmbedContentRequestJsonData))]
109 | [JsonSerializable(typeof(EmbedContentResponse))]
110 | [JsonSerializable(typeof(ContentEmbedding))]
111 | [JsonSerializable(typeof(TaskType))]
112 |
113 | // Live
114 | [JsonSerializable(typeof(BidiGenerateContent))]
115 | [JsonSerializable(typeof(ActivityEnd))]
116 | [JsonSerializable(typeof(ActivityStart))]
117 | [JsonSerializable(typeof(AudioTranscriptionConfig))]
118 | [JsonSerializable(typeof(AutomaticActivityDetection))]
119 | [JsonSerializable(typeof(BidiGenerateContentClientContent))]
120 | [JsonSerializable(typeof(BidiGenerateContentRealtimeInput))]
121 | [JsonSerializable(typeof(BidiGenerateContentServerContent))]
122 | [JsonSerializable(typeof(BidiGenerateContentServerMessage))]
123 | [JsonSerializable(typeof(BidiGenerateContentSetup))]
124 | [JsonSerializable(typeof(BidiGenerateContentSetupComplete))]
125 | [JsonSerializable(typeof(BidiGenerateContentToolCall))]
126 | [JsonSerializable(typeof(BidiGenerateContentToolCallCancellation))]
127 | [JsonSerializable(typeof(BidiGenerateContentToolResponse))]
128 | [JsonSerializable(typeof(BidiGenerateContentTranscription))]
129 | [JsonSerializable(typeof(ContextWindowCompressionConfig))]
130 | [JsonSerializable(typeof(GoAway))]
131 | [JsonSerializable(typeof(ProactivityConfig))]
132 | [JsonSerializable(typeof(RealtimeInputConfig))]
133 | [JsonSerializable(typeof(SessionResumptionConfig))]
134 | [JsonSerializable(typeof(SessionResumptionUpdate))]
135 | [JsonSerializable(typeof(SlidingWindow))]
136 | [JsonSerializable(typeof(UrlContextMetadata))]
137 | [JsonSerializable(typeof(UrlMetadata))]
138 | [JsonSerializable(typeof(LiveUsageMetadata))]
139 | [JsonSerializable(typeof(CreateAuthTokenRequest))]
140 | [JsonSerializable(typeof(AuthToken))]
141 | [JsonSerializable(typeof(ActivityHandling))]
142 | [JsonSerializable(typeof(EndSensitivity))]
143 | [JsonSerializable(typeof(StartSensitivity))]
144 | [JsonSerializable(typeof(TurnCoverage))]
145 | [JsonSerializable(typeof(UrlRetrievalStatus))]
146 | public partial class GemiNetJsonSerializerContext : JsonSerializerContext;
--------------------------------------------------------------------------------
/src/GemiNet/GenerateContentResponseExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace GemiNet;
2 |
3 | public static class GenerateContentResponseExtensions
4 | {
5 | public static string? GetText(this GenerateContentResponse response)
6 | {
7 | if (response.Candidates == null) return null;
8 |
9 | using var buffer = new PooledList(1024);
10 |
11 | var anyTextPartFound = false;
12 |
13 | foreach (var candidate in response.Candidates)
14 | {
15 | if (candidate.Content == null) continue;
16 |
17 | foreach (var part in candidate.Content.Parts)
18 | {
19 | if (part.Text != null)
20 | {
21 | buffer.AddRange(part.Text);
22 | anyTextPartFound = true;
23 | }
24 | }
25 | }
26 |
27 | if (!anyTextPartFound) return null;
28 |
29 | return buffer.AsSpan().ToString();
30 | }
31 |
32 | public static byte[]? GetData(this GenerateContentResponse response)
33 | {
34 | if (response.Candidates == null) return null;
35 |
36 | using var buffer = new PooledList(1024);
37 | var anyDataPartFound = false;
38 |
39 | foreach (var candidate in response.Candidates)
40 | {
41 | if (candidate.Content == null) continue;
42 |
43 | foreach (var part in candidate.Content.Parts)
44 | {
45 | if (part?.InlineData != null)
46 | {
47 | buffer.AddRange(Convert.FromBase64String(part.InlineData.Data));
48 | anyDataPartFound = true;
49 | }
50 | }
51 | }
52 |
53 | if (!anyDataPartFound) return null;
54 |
55 | return buffer.AsSpan().ToArray();
56 | }
57 |
58 | public static FunctionCall[] GetFunctionCalls(this GenerateContentResponse response)
59 | {
60 | if (response.Candidates == null) return [];
61 |
62 | return response.Candidates
63 | .SelectMany(x => x.Content?.Parts ?? [])
64 | .Where(x => x.FunctionCall != null)
65 | .Select(x => x.FunctionCall)
66 | .ToArray()!;
67 | }
68 |
69 | public static ExecutableCode? GetExecutableCode(this GenerateContentResponse response)
70 | {
71 | if (response.Candidates == null) return null;
72 |
73 | foreach (var candidate in response.Candidates)
74 | {
75 | if (candidate.Content == null) continue;
76 |
77 | foreach (var part in candidate.Content.Parts)
78 | {
79 | if (part?.ExecutableCode != null) return part.ExecutableCode;
80 | }
81 | }
82 |
83 | return null;
84 | }
85 |
86 |
87 | public static CodeExecutionResult? GetCodeExecutionResult(this GenerateContentResponse response)
88 | {
89 | if (response.Candidates == null) return null;
90 |
91 | foreach (var candidate in response.Candidates)
92 | {
93 | if (candidate.Content == null) continue;
94 |
95 | foreach (var part in candidate.Content.Parts)
96 | {
97 | if (part?.CodeExecutionResult != null) return part.CodeExecutionResult;
98 | }
99 | }
100 |
101 | return null;
102 | }
103 |
104 | public static string? GetText(this BidiGenerateContentServerMessage message)
105 | {
106 | using var buffer = new PooledList(1024);
107 |
108 | if (message.ServerContent == null ||
109 | message.ServerContent.ModelTurn == null ||
110 | message.ServerContent.ModelTurn.Parts.Length == 0)
111 | {
112 | return null;
113 | }
114 |
115 | var anyTextPartFound = false;
116 |
117 | foreach (var part in message.ServerContent.ModelTurn.Parts)
118 | {
119 | if (part.Text != null)
120 | {
121 | buffer.AddRange(part.Text);
122 | anyTextPartFound = true;
123 | }
124 | }
125 |
126 | if (!anyTextPartFound) return null;
127 |
128 | return buffer.AsSpan().ToString();
129 | }
130 |
131 | public static byte[]? GetData(this BidiGenerateContentServerMessage message)
132 | {
133 | using var buffer = new PooledList(1024);
134 |
135 | if (message.ServerContent == null ||
136 | message.ServerContent.ModelTurn == null ||
137 | message.ServerContent.ModelTurn.Parts.Length == 0)
138 | {
139 | return null;
140 | }
141 |
142 | var anyDataPartFound = false;
143 |
144 | foreach (var part in message.ServerContent.ModelTurn.Parts)
145 | {
146 | if (part.InlineData != null)
147 | {
148 | buffer.AddRange(Convert.FromBase64String(part.InlineData.Data));
149 | anyDataPartFound = true;
150 | }
151 | }
152 |
153 | if (!anyDataPartFound) return null;
154 |
155 | return buffer.AsSpan().ToArray();
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/GemiNet/GoogleGenAI.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http.Headers;
2 | using System.Net.Http.Json;
3 | using System.Runtime.CompilerServices;
4 | using System.Text;
5 | using System.Text.Json;
6 |
7 | namespace GemiNet;
8 |
9 | public class GoogleGenAI : IDisposable
10 | {
11 | sealed class GoogleGenAIModels(GoogleGenAI ai) : IModels
12 | {
13 | public async Task GetAsync(string name, CancellationToken cancellationToken = default)
14 | {
15 | var response = await ai.HttpClient.GetAsync($"{ai.BaseUrl}/{name}?key={ai.ApiKey}", cancellationToken)
16 | .ConfigureAwait(ai.ConfigureAwait);
17 |
18 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
19 |
20 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
21 | .ConfigureAwait(ai.ConfigureAwait);
22 |
23 | return result!;
24 | }
25 |
26 | public async Task ListAsync(ListModelsRequest request, CancellationToken cancellationToken = default)
27 | {
28 | var queryParameters = (request.PageSize, request.PageToken) switch
29 | {
30 | (int size, string token) => $"key={ai.ApiKey}&pageSize={size}&pageToken={token}",
31 | (int size, null) => $"key={ai.ApiKey}&pageSize={size}",
32 | (null, string token) => $"key={ai.ApiKey}&pageToken={token}",
33 | (null, null) => $"key={ai.ApiKey}",
34 | };
35 |
36 | var response = await ai.HttpClient.GetAsync($"{ai.BaseUrl}/models?{queryParameters}", cancellationToken)
37 | .ConfigureAwait(ai.ConfigureAwait);
38 |
39 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
40 |
41 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
42 | .ConfigureAwait(ai.ConfigureAwait);
43 |
44 | return result!;
45 | }
46 |
47 | public async Task GenerateContentAsync(GenerateContentRequest request, CancellationToken cancellationToken = default)
48 | {
49 | var response = await ai.HttpClient.PostAsJsonAsync(
50 | $"{ai.BaseUrl}/{request.Model}:generateContent?key={ai.ApiKey}",
51 | request,
52 | GemiNetJsonSerializerContext.Default.GetTypeInfo()!,
53 | cancellationToken)
54 | .ConfigureAwait(ai.ConfigureAwait);
55 |
56 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
57 |
58 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
59 | .ConfigureAwait(ai.ConfigureAwait);
60 |
61 | return result!;
62 | }
63 |
64 | public async IAsyncEnumerable GenerateContentStreamAsync(GenerateContentRequest request, [EnumeratorCancellation] CancellationToken cancellationToken)
65 | {
66 | var response = await ai.HttpClient.PostAsJsonAsync(
67 | $"{ai.BaseUrl}/{request.Model}:streamGenerateContent?key={ai.ApiKey}",
68 | request,
69 | GemiNetJsonSerializerContext.Default.GetTypeInfo()!,
70 | cancellationToken)
71 | .ConfigureAwait(ai.ConfigureAwait);
72 |
73 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
74 |
75 | #if NET6_0_OR_GREATER
76 | var stream = await response.Content.ReadAsStreamAsync(cancellationToken)
77 | #else
78 | var stream = await response.Content.ReadAsStreamAsync()
79 | #endif
80 | .ConfigureAwait(ai.ConfigureAwait);
81 |
82 | await foreach (var result in JsonSerializer.DeserializeAsyncEnumerable(stream, GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken))
83 | {
84 | if (result == null) continue;
85 | yield return result!;
86 | }
87 | }
88 |
89 | public async Task CountTokensAsync(CountTokensRequest request, CancellationToken cancellationToken = default)
90 | {
91 | var response = await ai.HttpClient.PostAsJsonAsync(
92 | $"{ai.BaseUrl}/{request.Model}:countTokens?key={ai.ApiKey}",
93 | request,
94 | GemiNetJsonSerializerContext.Default.GetTypeInfo()!,
95 | cancellationToken)
96 | .ConfigureAwait(ai.ConfigureAwait);
97 |
98 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
99 |
100 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
101 | .ConfigureAwait(ai.ConfigureAwait);
102 |
103 | return result!;
104 | }
105 |
106 | public async Task EmbedContentAsync(EmbedContentRequest request, CancellationToken cancellationToken = default)
107 | {
108 | HttpResponseMessage response;
109 | if (request.Contents.Count == 1)
110 | {
111 | response = await ai.HttpClient.PostAsJsonAsync(
112 | $"{ai.BaseUrl}/{request.Model}:embedContent?key={ai.ApiKey}",
113 | new()
114 | {
115 | Content = request.Contents[0],
116 | OutputDimensionality = request.OutputDimensionality,
117 | TaskType = request.TaskType,
118 | Title = request?.Title,
119 | },
120 | GemiNetJsonSerializerContext.Default.GetTypeInfo()!,
121 | cancellationToken)
122 | .ConfigureAwait(ai.ConfigureAwait);
123 | }
124 | else
125 | {
126 | var requests = new EmbedContentRequestJsonData[request.Contents.Count];
127 | for (int i = 0; i < requests.Length; i++)
128 | {
129 | requests[i] = new()
130 | {
131 | Content = request!.Contents[i],
132 | OutputDimensionality = request.OutputDimensionality,
133 | TaskType = request.TaskType,
134 | Title = request?.Title,
135 | };
136 | }
137 |
138 | response = await ai.HttpClient.PostAsJsonAsync(
139 | $"{ai.BaseUrl}/{request!.Model}:batchEmbedContents?key={ai.ApiKey}",
140 | new()
141 | {
142 | Requests = requests,
143 | },
144 | GemiNetJsonSerializerContext.Default.GetTypeInfo()!,
145 | cancellationToken)
146 | .ConfigureAwait(ai.ConfigureAwait);
147 | }
148 |
149 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
150 |
151 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
152 | .ConfigureAwait(ai.ConfigureAwait);
153 |
154 | return result!;
155 | }
156 | }
157 |
158 | // TODO: optimize
159 | sealed class GoogleGenAIFiles(GoogleGenAI ai) : IFiles
160 | {
161 | public Task UploadAsync(UploadFileRequest request, CancellationToken cancellationToken = default)
162 | {
163 | return request.File.Type switch
164 | {
165 | UploadFileContentType.Path => UploadFromFilePathAsync(request, request.File.Path!, cancellationToken),
166 | UploadFileContentType.Blob => UploadBlobAsync(request, request.File.Blob!, cancellationToken),
167 | _ => default!,
168 | };
169 | }
170 |
171 | async Task FetchUploadUrlAsync(File fileToUpload, CancellationToken cancellationToken)
172 | {
173 | var uri = $"https://generativelanguage.googleapis.com/upload/v1beta/files?key={ai.ApiKey}";
174 | var metadata = new GetUploadUrlRequest
175 | {
176 | File = fileToUpload,
177 | };
178 |
179 | var metadataJson = JsonSerializer.Serialize(metadata, GemiNetJsonSerializerContext.Default.GetTypeInfo()!);
180 | var metadataContent = new StringContent(metadataJson, Encoding.UTF8, "application/json");
181 | metadataContent.Headers.Add("X-Goog-Upload-Protocol", "resumable");
182 | metadataContent.Headers.Add("X-Goog-Upload-Command", "start");
183 | metadataContent.Headers.Add("X-Goog-Upload-Header-Content-Length", fileToUpload.SizeBytes);
184 | metadataContent.Headers.Add("X-Goog-Upload-Header-Content-Type", fileToUpload.MimeType);
185 |
186 | using var metaRequest = new HttpRequestMessage(HttpMethod.Post, uri)
187 | {
188 | Content = metadataContent
189 | };
190 |
191 | var metaResponse = await ai.HttpClient.SendAsync(metaRequest, cancellationToken)
192 | .ConfigureAwait(ai.ConfigureAwait);
193 |
194 | if (!metaResponse.IsSuccessStatusCode)
195 | {
196 | throw new GemiNetException("Upload URL not found");
197 | }
198 |
199 | if (!metaResponse.Headers.TryGetValues("X-Goog-Upload-URL", out var uploadUrls))
200 | {
201 | throw new GemiNetException("Upload URL not found.");
202 | }
203 |
204 | return uploadUrls.First();
205 | }
206 |
207 | async Task UploadFromFilePathAsync(UploadFileRequest request, string filePath, CancellationToken cancellationToken)
208 | {
209 | var fileSize = new FileInfo(filePath).Length;
210 |
211 | var metadata = new File()
212 | {
213 | Name = request.Name,
214 | DisplayName = request.DisplayName,
215 | MimeType = request.MimeType ?? Mime.GetMimeType(filePath),
216 | SizeBytes = fileSize.ToString(),
217 | };
218 |
219 | var uploadUrl = await FetchUploadUrlAsync(metadata, cancellationToken);
220 |
221 | using var content = new StreamContent(System.IO.File.Open(filePath, FileMode.Open, FileAccess.Read));
222 | content.Headers.ContentLength = fileSize;
223 | content.Headers.ContentType = new MediaTypeHeaderValue(metadata.MimeType);
224 | content.Headers.Add("X-Goog-Upload-Command", "upload, finalize");
225 | content.Headers.Add("X-Goog-Upload-Offset", "0");
226 |
227 | var response = await ai.HttpClient.PostAsync(uploadUrl, content, cancellationToken);
228 |
229 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
230 |
231 | #if NET6_0_OR_GREATER
232 | var stream = await response.Content.ReadAsStreamAsync(cancellationToken)
233 | #else
234 | var stream = await response.Content.ReadAsStreamAsync()
235 | #endif
236 | .ConfigureAwait(ai.ConfigureAwait);
237 |
238 | var file = (await JsonSerializer.DeserializeAsync(stream, GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)!
239 | .ConfigureAwait(ai.ConfigureAwait))!.File;
240 |
241 | if (response.Headers.TryGetValues("X-Goog-Upload-Status", out var finalStatusValues))
242 | {
243 | if (finalStatusValues.FirstOrDefault() != "final")
244 | {
245 | throw new GemiNetException("Failed to upload file: Upload status is not finalized.");
246 | }
247 | }
248 |
249 | return file!;
250 | }
251 |
252 | async Task UploadBlobAsync(UploadFileRequest request, Blob blob, CancellationToken cancellationToken)
253 | {
254 | var blobBytes = Convert.FromBase64String(blob.Data);
255 | var blobSize = blobBytes.Length;
256 |
257 | var metadata = new File()
258 | {
259 | Name = request.Name,
260 | DisplayName = request.DisplayName,
261 | MimeType = request.MimeType,
262 | SizeBytes = blobSize.ToString(),
263 | };
264 |
265 | var uploadUrl = await FetchUploadUrlAsync(metadata, cancellationToken);
266 |
267 | using var content = new ByteArrayContent(blobBytes);
268 | content.Headers.ContentLength = blobSize;
269 | content.Headers.ContentType = new(metadata.MimeType!);
270 | content.Headers.Add("X-Goog-Upload-Command", "upload, finalize");
271 | content.Headers.Add("X-Goog-Upload-Offset", "0");
272 |
273 | var response = await ai.HttpClient.PostAsync(uploadUrl, content, cancellationToken)
274 | .ConfigureAwait(ai.ConfigureAwait);
275 |
276 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
277 |
278 | var file = (await JsonSerializer.DeserializeAsync(
279 | #if NET6_0_OR_GREATER
280 | await response.Content.ReadAsStreamAsync(cancellationToken),
281 | #else
282 | await response.Content.ReadAsStreamAsync(),
283 | #endif
284 | GemiNetJsonSerializerContext.Default.GetTypeInfo()!,
285 | cancellationToken)!
286 | .ConfigureAwait(ai.ConfigureAwait))!.File;
287 |
288 | if (response.Headers.TryGetValues("X-Goog-Upload-Status", out var finalStatusValues))
289 | {
290 | if (finalStatusValues.FirstOrDefault() != "final")
291 | {
292 | throw new GemiNetException("Failed to upload file: Upload status is not finalized.");
293 | }
294 | }
295 |
296 | return file!;
297 | }
298 |
299 | public async Task GetAsync(GetFileRequest request, CancellationToken cancellationToken = default)
300 | {
301 | var response = await ai.HttpClient.GetAsync($"{ai.BaseUrl}/{request.Name}", cancellationToken)
302 | .ConfigureAwait(ai.ConfigureAwait);
303 |
304 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
305 |
306 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
307 | .ConfigureAwait(ai.ConfigureAwait);
308 |
309 | return result!;
310 | }
311 |
312 | public async Task ListAsync(ListFilesRequest request, CancellationToken cancellationToken = default)
313 | {
314 | var queryParameters = (request.PageSize, request.PageToken) switch
315 | {
316 | (int size, string token) => $"key={ai.ApiKey}&pageSize={size}&pageToken={token}",
317 | (int size, null) => $"key={ai.ApiKey}&pageSize={size}",
318 | (null, string token) => $"key={ai.ApiKey}&pageToken={token}",
319 | (null, null) => $"key={ai.ApiKey}",
320 | };
321 |
322 | var response = await ai.HttpClient.GetAsync($"{ai.BaseUrl}/files?{queryParameters}", cancellationToken)
323 | .ConfigureAwait(ai.ConfigureAwait);
324 |
325 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
326 |
327 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
328 | .ConfigureAwait(ai.ConfigureAwait);
329 |
330 | return result!;
331 | }
332 |
333 | public async Task DeleteAsync(DeleteFileRequest request, CancellationToken cancellationToken = default)
334 | {
335 | var response = await ai.HttpClient.DeleteAsync($"{ai.BaseUrl}/{request.Name}", cancellationToken)
336 | .ConfigureAwait(ai.ConfigureAwait);
337 |
338 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
339 | }
340 | }
341 |
342 | sealed class GoogleGenAICaches(GoogleGenAI ai) : ICaches
343 | {
344 | public async Task CreateAsync(CreateChachedContentRequest request, CancellationToken cancellationToken = default)
345 | {
346 | var content = new CachedContent
347 | {
348 | Model = request.Model,
349 | Contents = request.Contents,
350 | Tools = request.Tools,
351 | ExpireTime = request.ExpireTime,
352 | Ttl = request.Ttl,
353 | DisplayName = request.DisplayName,
354 | SystemInstruction = request.SystemInstruction,
355 | ToolConfig = request.ToolConfig,
356 | };
357 |
358 | var response = await ai.HttpClient.PostAsJsonAsync($"{ai.BaseUrl}/cachedContents?key={ai.ApiKey}", content, GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken);
359 |
360 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
361 |
362 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken);
363 | return result!;
364 | }
365 |
366 | public async Task ListAsync(ListCachedContentsRequest request, CancellationToken cancellationToken = default)
367 | {
368 | var queryParameters = (request.PageSize, request.PageToken) switch
369 | {
370 | (int size, string token) => $"key={ai.ApiKey}&pageSize={size}&pageToken={token}",
371 | (int size, null) => $"key={ai.ApiKey}&pageSize={size}",
372 | (null, string token) => $"key={ai.ApiKey}&pageToken={token}",
373 | (null, null) => $"key={ai.ApiKey}",
374 | };
375 |
376 | var response = await ai.HttpClient.GetAsync($"{ai.BaseUrl}/cachedContents?{queryParameters}", cancellationToken)
377 | .ConfigureAwait(ai.ConfigureAwait);
378 |
379 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
380 |
381 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
382 | .ConfigureAwait(ai.ConfigureAwait);
383 |
384 | return result!;
385 | }
386 |
387 | public async Task GetAsync(GetChachedContentRequest request, CancellationToken cancellationToken = default)
388 | {
389 | var response = await ai.HttpClient.GetAsync($"{ai.BaseUrl}/cachedContents?{request.Name}?key={ai.ApiKey}", cancellationToken)
390 | .ConfigureAwait(ai.ConfigureAwait);
391 |
392 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
393 |
394 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
395 | .ConfigureAwait(ai.ConfigureAwait);
396 |
397 | return result!;
398 | }
399 |
400 | public async Task PatchAsync(PatchChachedContentRequest request, CancellationToken cancellationToken = default)
401 | {
402 | var url = $"{ai.BaseUrl}/cachedContents?{request.Name}?key={ai.ApiKey}{(request.UpdateMask == null ? "" : $"?updateMask={request.UpdateMask}")}";
403 | var response = await ai.HttpClient.PatchAsJsonAsync(url, request.Config, GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
404 | .ConfigureAwait(ai.ConfigureAwait);
405 |
406 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
407 |
408 | var result = await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken)
409 | .ConfigureAwait(ai.ConfigureAwait);
410 |
411 | return result!;
412 | }
413 |
414 | public async Task DeleteAsync(DeleteChachedContentRequest request, CancellationToken cancellationToken = default)
415 | {
416 | var response = await ai.HttpClient.DeleteAsync($"{ai.BaseUrl}/cachedContents?{request.Name}?key={ai.ApiKey}", cancellationToken)
417 | .ConfigureAwait(ai.ConfigureAwait);
418 |
419 | if (!response.IsSuccessStatusCode) await ai.ThrowFromErrorResponseAsync(response, cancellationToken);
420 | }
421 | }
422 |
423 | sealed class GoogleGenAILive(GoogleGenAI ai) : ILive
424 | {
425 | public async Task ConnectAsync(BidiGenerateContentSetup request, CancellationToken cancellationToken = default)
426 | {
427 | var live = new LiveSession(ai);
428 | await live.ConnectAsync(request, cancellationToken);
429 | return live;
430 | }
431 | }
432 |
433 | public GoogleGenAI() : this(new HttpClientHandler(), true)
434 | {
435 | }
436 |
437 | public GoogleGenAI(HttpMessageHandler handler)
438 | : this(handler, true)
439 | {
440 | }
441 |
442 | public GoogleGenAI(HttpMessageHandler handler, bool disposeHandler)
443 | {
444 | HttpClient = new(handler, disposeHandler);
445 | Models = new GoogleGenAIModels(this);
446 | Files = new GoogleGenAIFiles(this);
447 | Caches = new GoogleGenAICaches(this);
448 | Live = new GoogleGenAILive(this);
449 | }
450 |
451 | public string ApiKey { get; set; } = Environment.GetEnvironmentVariable("GEMINI_API_KEY") ?? "";
452 |
453 | public bool ConfigureAwait { get; set; } = false;
454 |
455 | public IModels Models { get; }
456 | public IFiles Files { get; }
457 | public ICaches Caches { get; }
458 | public ILive Live { get; }
459 |
460 | public HttpClient HttpClient { get; }
461 |
462 | public string BaseUrl { get; } = "https://generativelanguage.googleapis.com/v1beta";
463 |
464 | async Task ThrowFromErrorResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken)
465 | {
466 | var error = (await response.Content.ReadFromJsonAsync(GemiNetJsonSerializerContext.Default.GetTypeInfo()!, cancellationToken).ConfigureAwait(ConfigureAwait))!.Error;
467 | throw new GemiNetException(error.Message);
468 | }
469 |
470 | public void Dispose()
471 | {
472 | HttpClient.Dispose();
473 | }
474 | }
--------------------------------------------------------------------------------
/src/GemiNet/ICaches.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace GemiNet;
4 |
5 | public interface ICaches
6 | {
7 | Task CreateAsync(CreateChachedContentRequest request, CancellationToken cancellationToken = default);
8 | Task ListAsync(ListCachedContentsRequest request, CancellationToken cancellationToken = default);
9 | Task GetAsync(GetChachedContentRequest request, CancellationToken cancellationToken = default);
10 | Task PatchAsync(PatchChachedContentRequest request, CancellationToken cancellationToken = default);
11 | }
12 |
13 | public static class CachesExtensions
14 | {
15 | public static async IAsyncEnumerable ListAsync(this ICaches caches, [EnumeratorCancellation] CancellationToken cancellationToken = default)
16 | {
17 | var request = new ListCachedContentsRequest();
18 |
19 | while (true)
20 | {
21 | var response = await caches.ListAsync(request, cancellationToken);
22 | foreach (var content in response.CachedContents ?? [])
23 | {
24 | yield return content;
25 | }
26 |
27 | if (response.NextPageToken == null) break;
28 |
29 | request = request with
30 | {
31 | PageToken = response.NextPageToken,
32 | };
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/GemiNet/IFiles.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace GemiNet;
4 |
5 | public interface IFiles
6 | {
7 | Task UploadAsync(UploadFileRequest request, CancellationToken cancellationToken = default);
8 | Task GetAsync(GetFileRequest request, CancellationToken cancellationToken = default);
9 | Task ListAsync(ListFilesRequest request, CancellationToken cancellationToken = default);
10 | Task DeleteAsync(DeleteFileRequest request, CancellationToken cancellationToken = default);
11 | }
12 |
13 | public static class FilesExtensions
14 | {
15 | public static async IAsyncEnumerable ListAsync(this IFiles files, [EnumeratorCancellation] CancellationToken cancellationToken = default)
16 | {
17 | var request = new ListFilesRequest();
18 |
19 | while (true)
20 | {
21 | var response = await files.ListAsync(request, cancellationToken);
22 | foreach (var file in response.Files)
23 | {
24 | yield return file;
25 | }
26 |
27 | if (response.NextPageToken == null) break;
28 | request = request with
29 | {
30 | PageToken = response.NextPageToken,
31 | };
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/GemiNet/ILive.cs:
--------------------------------------------------------------------------------
1 | namespace GemiNet;
2 |
3 | public interface ILive
4 | {
5 | Task ConnectAsync(BidiGenerateContentSetup request, CancellationToken cancellationToken = default);
6 | }
--------------------------------------------------------------------------------
/src/GemiNet/IModels.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace GemiNet;
4 |
5 | public interface IModels
6 | {
7 | Task GetAsync(string name, CancellationToken cancellationToken = default);
8 | Task ListAsync(ListModelsRequest request, CancellationToken cancellationToken = default);
9 | Task GenerateContentAsync(GenerateContentRequest request, CancellationToken cancellationToken = default);
10 | IAsyncEnumerable GenerateContentStreamAsync(GenerateContentRequest request, CancellationToken cancellationToken = default);
11 |
12 | Task CountTokensAsync(CountTokensRequest request, CancellationToken cancellationToken = default);
13 |
14 | Task EmbedContentAsync(EmbedContentRequest request, CancellationToken cancellationToken = default);
15 | }
16 |
17 | public static class ModelsExtensions
18 | {
19 | public static async IAsyncEnumerable ListAsync(this IModels models, [EnumeratorCancellation] CancellationToken cancellationToken = default)
20 | {
21 | var request = new ListModelsRequest();
22 |
23 | while (true)
24 | {
25 | var response = await models.ListAsync(request, cancellationToken);
26 | foreach (var model in response.Models)
27 | {
28 | yield return model;
29 | }
30 |
31 | if (response.NextPageToken == null) break;
32 | request = request with
33 | {
34 | PageToken = response.NextPageToken,
35 | };
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/GemiNet/Internal/Mime.tt:
--------------------------------------------------------------------------------
1 | <#@ template language="C#" debug="false" hostspecific="true" #>
2 | <#@ output extension=".g.cs" #>
3 | <#@ assembly name="System.Text.Json" #>
4 | <#@ import namespace="System" #>
5 | <#@ import namespace="System.IO" #>
6 | <#@ import namespace="System.Text.Json" #>
7 | <#@ import namespace="System.Collections.Generic" #>
8 | //
9 | #nullable enable
10 |
11 | using System;
12 | using System.Diagnostics.CodeAnalysis;
13 | using System.IO;
14 |
15 | namespace GemiNet;
16 |
17 | internal static partial class Mime
18 | {
19 | public static string GetMimeType(ReadOnlySpan filePath, string defaultType = "application/octet-stream")
20 | {
21 | if (TryGetMimeType(filePath, out var mimeType)) return mimeType;
22 | return defaultType;
23 | }
24 |
25 | public static bool TryGetMimeType(ReadOnlySpan filePath, [NotNullWhen(true)] out string? mimeType)
26 | {
27 | var temp = Path.GetExtension(filePath);
28 | Span ext = stackalloc char[temp.Length];
29 | temp.ToLowerInvariant(ext);
30 |
31 | switch (ext)
32 | {
33 | <#
34 | var jsonPath = Path.Combine(Path.GetDirectoryName(Host.TemplateFile), "mime.json");
35 | var json = File.ReadAllText(jsonPath);
36 | using var doc = JsonDocument.Parse(json);
37 |
38 | var extToMime = new Dictionary(StringComparer.OrdinalIgnoreCase);
39 |
40 | foreach (var prop in doc.RootElement.EnumerateObject())
41 | {
42 | var mime = prop.Name;
43 | if (prop.Value.TryGetProperty("extensions", out var exts))
44 | {
45 | foreach (var extElem in exts.EnumerateArray())
46 | {
47 | var extStr = extElem.GetString();
48 | if (!string.IsNullOrEmpty(extStr) && !extToMime.ContainsKey(extStr))
49 | {
50 | extToMime[extStr] = mime;
51 | }
52 | }
53 | }
54 | }
55 |
56 | foreach (var kv in extToMime)
57 | {
58 | #>
59 | case ".<#= kv.Key #>":
60 | mimeType = "<#= kv.Value #>";
61 | return true;
62 | <# } #>
63 | default:
64 | mimeType = null;
65 | return false;
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/src/GemiNet/Internal/PolyfillJsonStringEnumConverter.cs:
--------------------------------------------------------------------------------
1 | // Licensed to the .NET Foundation under one or more agreements.
2 | // The .NET Foundation licenses this file to you under the MIT license.
3 |
4 | using System.ComponentModel;
5 | using System.Diagnostics.CodeAnalysis;
6 | using System.Reflection;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 |
10 | namespace GemiNet;
11 |
12 | ///
13 | /// A JSON converter for enums that allows customizing the serialized string value of enum members
14 | /// using the .
15 | ///
16 | /// The enum type to convert.
17 | ///
18 | /// This is a temporary workaround for lack of System.Text.Json's JsonStringEnumConverter<T>
19 | /// 9.x support for custom enum member naming. It will be replaced by the built-in functionality
20 | /// once .NET 9 is fully adopted.
21 | ///
22 | [EditorBrowsable(EditorBrowsableState.Never)]
23 | #if NET6_0_OR_GREATER
24 | internal sealed class PolyfillJsonStringEnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum> :
25 | #else
26 | internal sealed class PolyfillJsonStringEnumConverter :
27 | #endif
28 | JsonStringEnumConverter where TEnum : struct, Enum
29 | {
30 | #if !NET9_0_OR_GREATER
31 | ///
32 | /// Initializes a new instance of the class.
33 | ///
34 | ///
35 | /// The converter automatically detects any enum members decorated with
36 | /// and uses those values during serialization and deserialization.
37 | ///
38 | public PolyfillJsonStringEnumConverter() :
39 | base(namingPolicy: ResolveNamingPolicy())
40 | {
41 | }
42 |
43 | private static JsonNamingPolicy? ResolveNamingPolicy()
44 | {
45 | var map = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)
46 | .Select(f => (f.Name, AttributeName: f.GetCustomAttribute()?.Name))
47 | .Where(pair => pair.AttributeName != null)
48 | .ToDictionary(pair => pair.Name, pair => pair.AttributeName);
49 |
50 | return map.Count > 0 ? new EnumMemberNamingPolicy(map!) : null;
51 | }
52 |
53 | private sealed class EnumMemberNamingPolicy(Dictionary map) : JsonNamingPolicy
54 | {
55 | public override string ConvertName(string name) =>
56 | map.TryGetValue(name, out string? newName) ?
57 | newName :
58 | name;
59 | }
60 | #endif
61 | }
62 |
63 | #if !NET9_0_OR_GREATER
64 | ///
65 | /// Determines the custom string value that should be used when serializing an enum member using JSON.
66 | ///
67 | ///
68 | /// This attribute is a temporary workaround for lack of System.Text.Json's support for custom enum member naming
69 | /// in versions prior to .NET 9. It works together with
70 | /// to provide customized string representations of enum values during JSON serialization and deserialization.
71 | ///
72 | [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
73 | internal sealed class JsonStringEnumMemberNameAttribute : Attribute
74 | {
75 | ///
76 | /// Creates new attribute instance with a specified enum member name.
77 | ///
78 | /// The name to apply to the current enum member when serialized to JSON.
79 | public JsonStringEnumMemberNameAttribute(string name)
80 | {
81 | Name = name;
82 | }
83 |
84 | ///
85 | /// Gets the custom JSON name of the enum member.
86 | ///
87 | public string Name { get; }
88 | }
89 | #endif
--------------------------------------------------------------------------------
/src/GemiNet/Internal/PooledList.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace GemiNet;
5 |
6 | internal ref struct PooledList
7 | {
8 | T[]? buffer;
9 | int tail;
10 |
11 | public PooledList(int sizeHint)
12 | {
13 | buffer = ArrayPool.Shared.Rent(sizeHint);
14 | }
15 |
16 | public bool IsDisposed => tail == -1;
17 | public int Count => tail;
18 |
19 | public void Add(in T item)
20 | {
21 | ThrowIfDisposed();
22 |
23 | if (buffer == null)
24 | {
25 | buffer = ArrayPool.Shared.Rent(32);
26 | }
27 | else if (buffer.Length == tail)
28 | {
29 | var newArray = ArrayPool.Shared.Rent(tail * 2);
30 | buffer.AsSpan().CopyTo(newArray);
31 | ArrayPool.Shared.Return(buffer);
32 | buffer = newArray;
33 | }
34 |
35 | buffer[tail] = item;
36 | tail++;
37 | }
38 |
39 | public void AddRange(scoped ReadOnlySpan items)
40 | {
41 | ThrowIfDisposed();
42 |
43 | if (buffer == null)
44 | {
45 | buffer = ArrayPool.Shared.Rent(items.Length);
46 | }
47 | else if (buffer.Length < tail + items.Length)
48 | {
49 | var newSize = buffer.Length * 2;
50 | while (newSize < tail + items.Length)
51 | {
52 | newSize *= 2;
53 | }
54 |
55 | var newArray = ArrayPool.Shared.Rent(newSize);
56 | buffer.AsSpan().CopyTo(newArray);
57 | ArrayPool.Shared.Return(buffer);
58 | buffer = newArray;
59 | }
60 |
61 | items.CopyTo(buffer.AsSpan()[tail..]);
62 | tail += items.Length;
63 | }
64 |
65 | public void Clear()
66 | {
67 | ThrowIfDisposed();
68 |
69 | if (buffer != null)
70 | {
71 | new Span(buffer, 0, tail).Clear();
72 | }
73 |
74 | tail = 0;
75 | }
76 |
77 | public void Dispose()
78 | {
79 | ThrowIfDisposed();
80 |
81 | if (buffer != null)
82 | {
83 | ArrayPool.Shared.Return(buffer);
84 | buffer = null;
85 | }
86 |
87 | tail = -1;
88 | }
89 |
90 | public T this[int index]
91 | {
92 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
93 | get
94 | {
95 | return AsSpan()[index];
96 | }
97 | }
98 |
99 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
100 | public ReadOnlySpan AsSpan()
101 | {
102 | return new ReadOnlySpan(buffer, 0, tail);
103 | }
104 |
105 | void ThrowIfDisposed()
106 | {
107 | if (tail == -1) ThrowDisposedException();
108 | }
109 |
110 | static void ThrowDisposedException()
111 | {
112 | throw new ObjectDisposedException(nameof(PooledList));
113 | }
114 | }
--------------------------------------------------------------------------------
/src/GemiNet/JsonSerializerContextExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using System.Text.Json.Serialization;
3 | using System.Text.Json.Serialization.Metadata;
4 |
5 | namespace GemiNet;
6 |
7 | internal static class JsonSerializerContextExtensions
8 | {
9 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
10 | public static JsonTypeInfo? GetTypeInfo(this JsonSerializerContext context)
11 | {
12 | return (JsonTypeInfo?)context.GetTypeInfo(typeof(T));
13 | }
14 | }
--------------------------------------------------------------------------------
/src/GemiNet/LiveSession.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.Net.WebSockets;
3 | using System.Runtime.CompilerServices;
4 | using System.Text.Json;
5 |
6 | namespace GemiNet;
7 |
8 | public interface ILiveSession : IAsyncDisposable
9 | {
10 | ValueTask ConnectAsync(BidiGenerateContentSetup request, CancellationToken cancellationToken = default);
11 | ValueTask SendClientContentAsync(BidiGenerateContentClientContent content, CancellationToken cancellationToken = default);
12 | ValueTask SendRealtimeInputAsync(BidiGenerateContentRealtimeInput input, CancellationToken cancellationToken = default);
13 | ValueTask SendToolResponseAsync(BidiGenerateContentToolResponse toolResponse, CancellationToken cancellationToken = default);
14 | IAsyncEnumerable ReceiveAsync(CancellationToken cancellationToken = default);
15 | }
16 |
17 | sealed class LiveSession(GoogleGenAI ai) : ILiveSession
18 | {
19 | readonly ClientWebSocket socket = new();
20 | readonly CancellationTokenSource cts = new();
21 |
22 | public async ValueTask ConnectAsync(BidiGenerateContentSetup setup, CancellationToken cancellationToken = default)
23 | {
24 | var url = $"wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent?key={ai.ApiKey}";
25 | await socket.ConnectAsync(new Uri(url), cancellationToken);
26 |
27 | var json = JsonSerializer.SerializeToUtf8Bytes(new()
28 | {
29 | Setup = setup,
30 | }, GemiNetJsonSerializerContext.Default.GetTypeInfo()!);
31 |
32 | await socket.SendAsync(json.AsMemory(), WebSocketMessageType.Text, true, cancellationToken);
33 | }
34 |
35 | public async ValueTask SendRealtimeInputAsync(BidiGenerateContentRealtimeInput realtimeInput, CancellationToken cancellationToken = default)
36 | {
37 | var json = JsonSerializer.SerializeToUtf8Bytes(new()
38 | {
39 | RealtimeInput = realtimeInput
40 | }, GemiNetJsonSerializerContext.Default.GetTypeInfo()!);
41 |
42 | await socket.SendAsync(json.AsMemory(), WebSocketMessageType.Text, true, cancellationToken);
43 | }
44 |
45 | public async ValueTask SendClientContentAsync(BidiGenerateContentClientContent clientContent, CancellationToken cancellationToken = default)
46 | {
47 | var json = JsonSerializer.SerializeToUtf8Bytes(new()
48 | {
49 | ClientContent = clientContent,
50 | }, GemiNetJsonSerializerContext.Default.GetTypeInfo()!);
51 |
52 | await socket.SendAsync(json.AsMemory(), WebSocketMessageType.Text, true, cancellationToken);
53 | }
54 |
55 | public async ValueTask SendToolResponseAsync(BidiGenerateContentToolResponse toolResponse, CancellationToken cancellationToken = default)
56 | {
57 | var json = JsonSerializer.SerializeToUtf8Bytes(new()
58 | {
59 | ToolResponse = toolResponse,
60 | }, GemiNetJsonSerializerContext.Default.GetTypeInfo()!);
61 |
62 | await socket.SendAsync(json.AsMemory(), WebSocketMessageType.Text, true, cancellationToken);
63 | }
64 |
65 | public async IAsyncEnumerable ReceiveAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
66 | {
67 | while (socket.State == WebSocketState.Open)
68 | {
69 | cts.Token.ThrowIfCancellationRequested();
70 |
71 | var buffer = ArrayPool.Shared.Rent(8192);
72 | var bytesConsumed = 0;
73 | try
74 | {
75 | ValueWebSocketReceiveResult result;
76 | do
77 | {
78 | result = await socket.ReceiveAsync(buffer.AsMemory(bytesConsumed), cts.Token);
79 | bytesConsumed += result.Count;
80 | } while (!result.EndOfMessage);
81 | }
82 | finally
83 | {
84 | ArrayPool.Shared.Return(buffer);
85 | }
86 |
87 | var msg = JsonSerializer.Deserialize(
88 | buffer.AsSpan(0, bytesConsumed),
89 | GemiNetJsonSerializerContext.Default.GetTypeInfo()!);
90 |
91 | if (msg != null) yield return msg;
92 | }
93 | }
94 |
95 | public async ValueTask DisposeAsync()
96 | {
97 | if (socket.State == WebSocketState.Open)
98 | {
99 | await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", cts.Token);
100 | }
101 | socket.Dispose();
102 |
103 | cts.Cancel();
104 | cts.Dispose();
105 | }
106 | }
--------------------------------------------------------------------------------
/src/GemiNet/Models.cs:
--------------------------------------------------------------------------------
1 | namespace GemiNet;
2 |
3 | // https://ai.google.dev/gemini-api/docs/models
4 |
5 | public static class Models
6 | {
7 | public const string Gemini2_5FlashPreview = "models/gemini-2.5-flash-preview-05-20";
8 | public const string Gemini2_5FlashPreviewNativeAudioDialog = "models/gemini-2.5-flash-preview-native-audio-dialog";
9 | public const string Gemini2_5FlashPreviewTts = "models/gemini-2.5-flash-preview-tts";
10 | public const string Gemini2_5ProPreview = "models/gemini-2.5-pro-preview-05-06";
11 | public const string Gemini2_5ProPreviewTts = "models/gemini-2.5-pro-preview-tts";
12 | public const string Gemini2_0Flash = "models/gemini-2.0-flash";
13 | public const string Gemini2_0FlashPreviewImageGeneration = "gemini-2.0-flash-preview-image-generation";
14 | public const string Gemini2_0FlashLite = "models/gemini-2.0-flash-lite";
15 | public const string Gemini1_5Flash = "models/gemini-1.5-flash";
16 | public const string Gemini1_5Flash8B = "models/gemini-1.5-flash-8b";
17 | public const string Gemini1_5Pro = "models/gemini-1.5-pro";
18 | public const string GeminiEmbeddingExp = "models/gemini-embedding-exp";
19 | public const string Imagen3 = "models/imagen-3.0-generate-002";
20 | public const string Veo2 = "models/veo-2.0-generate-001";
21 | public const string Gemini2_0FlashLive = "models/gemini-2.0-flash-live-001";
22 | }
--------------------------------------------------------------------------------
/src/GemiNet/Models/Caching.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization;
4 |
5 | namespace GemiNet;
6 |
7 | public record CreateChachedContentRequest
8 | {
9 | public required string Model { get; init; }
10 | public Contents? Contents { get; init; }
11 | public Tool[]? Tools { get; init; }
12 | public string? ExpireTime { get; init; }
13 | public string? Ttl { get; init; }
14 | public string? DisplayName { get; init; }
15 | public Content? SystemInstruction { get; init; }
16 | public ToolConfig? ToolConfig { get; init; }
17 | }
18 |
19 | public record ListCachedContentsRequest
20 | {
21 | public int? PageSize { get; init; }
22 | public string? PageToken { get; init; }
23 | }
24 |
25 | public record ListCachedContentsResponse
26 | {
27 | [JsonPropertyName("cachedContents")]
28 | public CachedContent[]? CachedContents { get; init; }
29 |
30 | [JsonPropertyName("nextPageToken")]
31 | public string? NextPageToken { get; init; }
32 | }
33 |
34 | public record GetChachedContentRequest
35 | {
36 | public required string Name { get; init; }
37 | }
38 |
39 | public record PatchChachedContentRequest
40 | {
41 | public required string Name { get; init; }
42 | public string? UpdateMask { get; init; }
43 | public PatchChachedContentConfig? Config { get; init; }
44 | }
45 |
46 | public record DeleteChachedContentRequest
47 | {
48 | public required string Name { get; init; }
49 | }
50 |
51 | public record PatchChachedContentConfig
52 | {
53 | [JsonPropertyName("expireTime")]
54 | public string? ExpireTime { get; init; }
55 |
56 | [JsonPropertyName("ttl")]
57 | public string? Ttl { get; init; }
58 | }
59 |
60 | public record CachedContent
61 | {
62 | [JsonPropertyName("contents")]
63 | public Contents? Contents { get; init; }
64 |
65 | [JsonPropertyName("tools")]
66 | public Tool[]? Tools { get; init; }
67 |
68 | [JsonPropertyName("createTime")]
69 | public string? CreateTime { get; init; }
70 |
71 | [JsonPropertyName("updateTime")]
72 | public string? UpdateTime { get; init; }
73 |
74 | [JsonPropertyName("usageMetadata")]
75 | public CachingUsageMetadata? UsageMetadata { get; init; }
76 |
77 | [JsonPropertyName("expireTime")]
78 | public string? ExpireTime { get; init; }
79 |
80 | [JsonPropertyName("ttl")]
81 | public string? Ttl { get; init; }
82 |
83 | [JsonPropertyName("name")]
84 | public string? Name { get; init; }
85 |
86 | [JsonPropertyName("displayName")]
87 | public string? DisplayName { get; init; }
88 |
89 | [JsonPropertyName("model")]
90 | public required string Model { get; init; }
91 |
92 | [JsonPropertyName("systemInstruction")]
93 | public Content? SystemInstruction { get; init; }
94 |
95 | [JsonPropertyName("toolConfig")]
96 | public ToolConfig? ToolConfig { get; init; }
97 | }
98 |
99 | public record Content
100 | {
101 | [JsonPropertyName("parts")]
102 | public required Part[] Parts { get; init; }
103 |
104 | [JsonPropertyName("role")]
105 | public string? Role { get; init; }
106 |
107 | public static implicit operator Content(string text)
108 | {
109 | return CreateUserContent(text);
110 | }
111 |
112 | public static Content CreateUserContent(string text)
113 | {
114 | return new()
115 | {
116 | Parts = [Part.FromText(text)],
117 | Role = "user",
118 | };
119 | }
120 |
121 | public static Content CreateUserContent(params Part[] parts)
122 | {
123 | return new()
124 | {
125 | Parts = parts,
126 | Role = "user",
127 | };
128 | }
129 |
130 |
131 | public static Content CreateModelContent(string text)
132 | {
133 | return new()
134 | {
135 | Parts = [Part.FromText(text)],
136 | Role = "model",
137 | };
138 | }
139 |
140 | public static Content CreateModelContent(params Part[] parts)
141 | {
142 | return new()
143 | {
144 | Parts = parts,
145 | Role = "model",
146 | };
147 | }
148 | }
149 |
150 | public class Contents : Collection
151 | {
152 | public static implicit operator Contents(string text)
153 | {
154 | return [(Content)text];
155 | }
156 |
157 | public static implicit operator Contents(Content content)
158 | {
159 | return [content];
160 | }
161 | }
162 |
163 | public record Part
164 | {
165 | [JsonPropertyName("thought")]
166 | public bool? Thought { get; init; }
167 |
168 | [JsonPropertyName("text")]
169 | public string? Text { get; init; }
170 |
171 | [JsonPropertyName("inlineData")]
172 | public Blob? InlineData { get; init; }
173 |
174 | [JsonPropertyName("functionCall")]
175 | public FunctionCall? FunctionCall { get; init; }
176 |
177 | [JsonPropertyName("functionResponse")]
178 | public FunctionResponse? FunctionResponse { get; init; }
179 |
180 | [JsonPropertyName("fileData")]
181 | public FileData? FileData { get; init; }
182 |
183 | [JsonPropertyName("executableCode")]
184 | public ExecutableCode? ExecutableCode { get; init; }
185 |
186 | [JsonPropertyName("codeExecutionResult")]
187 | public CodeExecutionResult? CodeExecutionResult { get; init; }
188 |
189 | public static Part FromBase64(string data, string mimeType)
190 | {
191 | return new()
192 | {
193 | InlineData = new()
194 | {
195 | Data = data,
196 | MimeType = mimeType,
197 | }
198 | };
199 | }
200 |
201 | public static Part FromCodeExecutionResult(CodeExecutionOutcome outcome, string output)
202 | {
203 | return new()
204 | {
205 | CodeExecutionResult = new()
206 | {
207 | Outcome = outcome,
208 | Code = output,
209 | }
210 | };
211 | }
212 |
213 | public static Part FromExecutableCode(string code, ExecutableCodeLanguage language)
214 | {
215 | return new()
216 | {
217 | ExecutableCode = new()
218 | {
219 | Code = code,
220 | Language = language,
221 | },
222 | };
223 | }
224 |
225 | public static Part FromFunctionCall(string name, JsonElement arguments)
226 | {
227 | return new()
228 | {
229 | FunctionCall = new()
230 | {
231 | Name = name,
232 | Args = arguments,
233 | }
234 | };
235 | }
236 |
237 | public static Part FromFunctionResponse(string id, string name, JsonElement response)
238 | {
239 | return new()
240 | {
241 | FunctionResponse = new()
242 | {
243 | Name = name,
244 | Id = id,
245 | Response = response,
246 | },
247 | };
248 | }
249 |
250 | public static Part FromText(string text)
251 | {
252 | return new()
253 | {
254 | Text = text,
255 | };
256 | }
257 |
258 | public static Part FromUri(string uri, string mimeType)
259 | {
260 | return new()
261 | {
262 | FileData = new()
263 | {
264 | FileUri = uri,
265 | MimeType = mimeType
266 | }
267 | };
268 | }
269 |
270 | public static Part FromUri(Uri uri, string mimeType)
271 | {
272 | return new()
273 | {
274 | FileData = new()
275 | {
276 | FileUri = uri.AbsoluteUri,
277 | MimeType = mimeType
278 | }
279 | };
280 | }
281 | }
282 |
283 | public record Blob
284 | {
285 | [JsonPropertyName("mimeType")]
286 | public required string MimeType { get; init; }
287 |
288 | [JsonPropertyName("data")]
289 | public required string Data { get; init; }
290 | }
291 |
292 | public record FunctionCall
293 | {
294 | [JsonPropertyName("id")]
295 | public string? Id { get; init; }
296 |
297 | [JsonPropertyName("name")]
298 | public required string Name { get; init; }
299 |
300 | [JsonPropertyName("args")]
301 | public JsonElement? Args { get; init; }
302 | }
303 |
304 | public record FunctionResponse
305 | {
306 | [JsonPropertyName("id")]
307 | public string? Id { get; init; }
308 |
309 | [JsonPropertyName("name")]
310 | public required string Name { get; init; }
311 |
312 | [JsonPropertyName("response")]
313 | public JsonElement? Response { get; init; }
314 | }
315 |
316 | public record FileData
317 | {
318 | [JsonPropertyName("mimeType")]
319 | public string? MimeType { get; init; }
320 |
321 | [JsonPropertyName("fileUri")]
322 | public required string FileUri { get; init; }
323 | }
324 |
325 | public record ExecutableCode
326 | {
327 | [JsonPropertyName("language")]
328 | public required ExecutableCodeLanguage Language { get; init; }
329 |
330 | [JsonPropertyName("code")]
331 | public required string Code { get; init; }
332 | }
333 |
334 | public enum ExecutableCodeLanguage
335 | {
336 | [JsonStringEnumMemberName("LANGUAGE_UNSPECIFIED")]
337 | Unspecified,
338 |
339 | [JsonStringEnumMemberName("PYTHON")]
340 | Python,
341 | }
342 |
343 | public record CodeExecutionResult
344 | {
345 | [JsonPropertyName("outcome")]
346 | public required CodeExecutionOutcome Outcome { get; init; }
347 |
348 | [JsonPropertyName("code")]
349 | public string? Code { get; init; }
350 | }
351 |
352 | public enum CodeExecutionOutcome
353 | {
354 | [JsonStringEnumMemberName("OUTCOME_UNSPECIFIED")]
355 | Unspecified,
356 |
357 | [JsonStringEnumMemberName("OUTCOME_OK")]
358 | Ok,
359 |
360 | [JsonStringEnumMemberName("OUTCOME_FAILED")]
361 | Failed,
362 |
363 | [JsonStringEnumMemberName("OUTCOME_DEADLINE_EXCEEDED")]
364 | DeadlineExceeded,
365 | }
366 |
367 | public record Tool
368 | {
369 | [JsonPropertyName("functionDeclarations")]
370 | public FunctionDeclaration[]? FunctionDeclarations { get; init; }
371 |
372 | [JsonPropertyName("googleSearchRetrieval")]
373 | public GoogleSearchRetrieval? GoogleSearchRetrieval { get; init; }
374 |
375 | [JsonPropertyName("codeExecution")]
376 | public CodeExecution? CodeExecution { get; init; }
377 |
378 | [JsonPropertyName("googleSearch")]
379 | public GoogleSearch? GoogleSearch { get; init; }
380 | }
381 |
382 | public record FunctionDeclaration
383 | {
384 | [JsonPropertyName("name")]
385 | public required string Name { get; init; }
386 |
387 | [JsonPropertyName("description")]
388 | public required string Description { get; init; }
389 |
390 | [JsonPropertyName("parameters")]
391 | public Schema? Parameters { get; init; }
392 |
393 | [JsonPropertyName("response")]
394 | public Schema? Response { get; init; }
395 | }
396 |
397 | public record Schema
398 | {
399 | [JsonPropertyName("type")]
400 | public required DataType Type { get; init; }
401 |
402 | [JsonPropertyName("format")]
403 | public string? Format { get; init; }
404 |
405 | [JsonPropertyName("title")]
406 | public string? Title { get; init; }
407 |
408 | [JsonPropertyName("description")]
409 | public string? Description { get; init; }
410 |
411 | [JsonPropertyName("nullable")]
412 | public bool? Nullable { get; init; }
413 |
414 | [JsonPropertyName("enum")]
415 | public string[]? Enum { get; init; }
416 |
417 | [JsonPropertyName("maxItems")]
418 | public string? MaxItems { get; init; }
419 |
420 | [JsonPropertyName("minItems")]
421 | public string? MinItems { get; init; }
422 |
423 | [JsonPropertyName("properties")]
424 | public Dictionary? Properties { get; init; }
425 |
426 | [JsonPropertyName("required")]
427 | public string[]? Required { get; init; }
428 |
429 | [JsonPropertyName("minProperties")]
430 | public string? MinProperties { get; init; }
431 |
432 | [JsonPropertyName("maxProperties")]
433 | public string? MaxProperties { get; init; }
434 |
435 | [JsonPropertyName("minLength")]
436 | public string? MinLength { get; init; }
437 |
438 | [JsonPropertyName("maxLength")]
439 | public string? MaxLength { get; init; }
440 |
441 | [JsonPropertyName("pattern")]
442 | public string? Pattern { get; init; }
443 |
444 | [JsonPropertyName("example")]
445 | public string? Example { get; init; }
446 |
447 | [JsonPropertyName("anyOf")]
448 | public Schema[]? AnyOf { get; init; }
449 |
450 | [JsonPropertyName("propertyOrdering")]
451 | public string[]? PropertyOrdering { get; init; }
452 |
453 | [JsonPropertyName("default")]
454 | public JsonElement? Default { get; init; }
455 |
456 | [JsonPropertyName("items")]
457 | public Schema[]? Items { get; init; }
458 |
459 | [JsonPropertyName("minimum")]
460 | public double? Minimum { get; init; }
461 |
462 | [JsonPropertyName("maximum")]
463 | public double? Maximum { get; init; }
464 |
465 | public static Schema FromJsonElement(JsonElement element)
466 | {
467 | return JsonSerializer.Deserialize(element, GemiNetJsonSerializerContext.Default.GetTypeInfo()!)
468 | ?? new() { Type = DataType.Unspecified };
469 | }
470 | }
471 |
472 |
473 | public enum DataType
474 | {
475 | [JsonStringEnumMemberName("TYPE_UNSPECIFIED")]
476 | Unspecified,
477 |
478 | [JsonStringEnumMemberName("STRING")]
479 | String,
480 |
481 | [JsonStringEnumMemberName("NUMBER")]
482 | Number,
483 |
484 | [JsonStringEnumMemberName("INTEGER")]
485 | Integer,
486 |
487 | [JsonStringEnumMemberName("BOOLEAN")]
488 | Boolean,
489 |
490 | [JsonStringEnumMemberName("ARRAY")]
491 | Array,
492 |
493 | [JsonStringEnumMemberName("OBJECT")]
494 | Object,
495 |
496 | [JsonStringEnumMemberName("NULL")]
497 | Null,
498 | }
499 |
500 |
501 | public record GoogleSearchRetrieval
502 | {
503 | [JsonPropertyName("dynamicRetrievalConfig")]
504 | public required DynamicRetrievalConfig DynamicRetrievalConfig { get; init; }
505 | }
506 |
507 | public record DynamicRetrievalConfig
508 | {
509 | [JsonPropertyName("mode")]
510 | public required DynamicRetrievalMode Mode { get; init; }
511 |
512 | [JsonPropertyName("dynamicThreshold")]
513 | public required double DynamicThreshold { get; init; }
514 | }
515 |
516 | public enum DynamicRetrievalMode
517 | {
518 | [JsonStringEnumMemberName("MODE_UNSPECIFIED")]
519 | Unspecified,
520 |
521 | [JsonStringEnumMemberName("MODE_DYNAMIC")]
522 | Dynamic,
523 | }
524 |
525 | public record CodeExecution;
526 | public record GoogleSearch;
527 |
528 |
529 | public record ToolConfig
530 | {
531 | [JsonPropertyName("functionCallingConfig")]
532 | public required FunctionCallingConfig FunctionCallingConfig { get; init; }
533 | }
534 |
535 | public record FunctionCallingConfig
536 | {
537 | [JsonPropertyName("mode")]
538 | public required FunctionCallingMode Mode { get; init; }
539 |
540 | [JsonPropertyName("allowedFunctionNames")]
541 | public required string[] AllowedFunctionNames { get; init; }
542 | }
543 |
544 | public enum FunctionCallingMode
545 | {
546 | [JsonStringEnumMemberName("MODE_UNSPECIFIED")]
547 | Unspecified,
548 |
549 | [JsonStringEnumMemberName("AUTO")]
550 | Auto,
551 |
552 | [JsonStringEnumMemberName("ANY")]
553 | Any,
554 |
555 | [JsonStringEnumMemberName("NONE")]
556 | None,
557 |
558 | [JsonStringEnumMemberName("VALIDATED")]
559 | Validated,
560 | }
561 |
562 | public record CachingUsageMetadata
563 | {
564 | [JsonPropertyName("totalTokenCount")]
565 | public required int TotalTokenCount { get; init; }
566 | }
--------------------------------------------------------------------------------
/src/GemiNet/Models/Embeddings.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace GemiNet;
4 |
5 | public record EmbedContentRequest
6 | {
7 | public required string Model { get; init; }
8 | public required Contents Contents { get; init; }
9 | public TaskType? TaskType { get; init; }
10 | public string? Title { get; init; }
11 | public int? OutputDimensionality { get; init; }
12 | }
13 |
14 | public record EmbedContentRequestJsonData
15 | {
16 | [JsonPropertyName("content")]
17 | public required Content Content { get; init; }
18 |
19 | [JsonPropertyName("taskType")]
20 | public TaskType? TaskType { get; init; }
21 |
22 | [JsonPropertyName("title")]
23 | public string? Title { get; init; }
24 |
25 | [JsonPropertyName("outputDimensionality")]
26 | public int? OutputDimensionality { get; init; }
27 | }
28 |
29 | public record BatchEmbedContentRequestJsonData
30 | {
31 | [JsonPropertyName("requests")]
32 | public required EmbedContentRequestJsonData[] Requests { get; init; }
33 | }
34 |
35 | public record EmbedContentResponse
36 | {
37 | [JsonPropertyName("embeddings")]
38 | public required ContentEmbedding[] Embeddings { get; init; }
39 | }
40 |
41 | public record ContentEmbedding
42 | {
43 | [JsonPropertyName("values")]
44 | public required double[] Values { get; init; }
45 | }
46 |
47 | public enum TaskType
48 | {
49 | [JsonStringEnumMemberName("TASK_TYPE_UNSPECIFIED")]
50 | Unspecified,
51 |
52 | [JsonStringEnumMemberName("RETRIEVAL_QUERY")]
53 | RetrievalQuery,
54 |
55 | [JsonStringEnumMemberName("RETRIEVAL_DOCUMENT")]
56 | RetrievalDocument,
57 |
58 | [JsonStringEnumMemberName("SEMANTIC_SIMILARITY")]
59 | SemanticSimilarity,
60 |
61 | [JsonStringEnumMemberName("CLASSIFICATION")]
62 | Classification,
63 |
64 | [JsonStringEnumMemberName("CLUSTERING")]
65 | Clustering,
66 |
67 | [JsonStringEnumMemberName("QUESTION_ANSWERING")]
68 | QuestionAnswering,
69 |
70 | [JsonStringEnumMemberName("FACT_VERIFICATION")]
71 | FactVerification,
72 |
73 | [JsonStringEnumMemberName("CODE_RETRIEVAL_QUERY")]
74 | CodeRetrievalQuery,
75 | }
--------------------------------------------------------------------------------
/src/GemiNet/Models/Files.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace GemiNet;
5 |
6 | public record UploadFileRequest
7 | {
8 | public required UploadFileContent File { get; init; }
9 | public string? Name { get; init; }
10 | public string? MimeType { get; init; }
11 | public string? DisplayName { get; init; }
12 | }
13 |
14 | public record UploadFileResponse
15 | {
16 | [JsonPropertyName("file")]
17 | public required File File { get; init; }
18 | }
19 |
20 | public record GetUploadUrlRequest
21 | {
22 | [JsonPropertyName("file")]
23 | public required File File { get; init; }
24 | }
25 |
26 | public record GetFileRequest
27 | {
28 | public required string Name { get; init; }
29 | }
30 |
31 | public record ListFilesRequest
32 | {
33 | public int? PageSize { get; init; }
34 | public string? PageToken { get; init; }
35 | }
36 |
37 | public record ListFilesResponse
38 | {
39 | [JsonPropertyName("files")]
40 | public required File[] Files { get; init; }
41 |
42 | [JsonPropertyName("nextPageToken")]
43 | public string? NextPageToken { get; init; }
44 | }
45 |
46 |
47 | public record DeleteFileRequest
48 | {
49 | public required string Name { get; init; }
50 | }
51 |
52 | public record DeleteFileResponse
53 | {
54 | [JsonPropertyName("file")]
55 | public required File File { get; init; }
56 | }
57 |
58 | [StructLayout(LayoutKind.Auto)]
59 | public readonly record struct UploadFileContent
60 | {
61 | public UploadFileContentType Type { get; }
62 | public string? Path { get; }
63 | public Blob? Blob { get; }
64 |
65 | public UploadFileContent(string path)
66 | {
67 | Type = UploadFileContentType.Path;
68 | Path = path;
69 | }
70 |
71 | public UploadFileContent(Blob blob)
72 | {
73 | Type = UploadFileContentType.Blob;
74 | Blob = blob;
75 | }
76 |
77 | public static implicit operator UploadFileContent(string path) => new(path);
78 | public static implicit operator UploadFileContent(Blob blob) => new(blob);
79 | }
80 |
81 | public enum UploadFileContentType : byte
82 | {
83 | Path,
84 | Blob
85 | }
86 |
87 | public record File
88 | {
89 | [JsonPropertyName("name")]
90 | public string? Name { get; init; }
91 |
92 | [JsonPropertyName("displayName")]
93 | public string? DisplayName { get; init; }
94 |
95 | [JsonPropertyName("mimeType")]
96 | public string? MimeType { get; init; }
97 |
98 | [JsonPropertyName("sizeBytes")]
99 | public string? SizeBytes { get; init; }
100 |
101 | [JsonPropertyName("createTime")]
102 | public string? CreateTime { get; init; }
103 |
104 | [JsonPropertyName("updateTime")]
105 | public string? UpdateTime { get; init; }
106 |
107 | [JsonPropertyName("expirationTime")]
108 | public string? ExpirationTime { get; init; }
109 |
110 | [JsonPropertyName("sha256Hash")]
111 | public string? Sha256Hash { get; init; }
112 |
113 | [JsonPropertyName("uri")]
114 | public string? Uri { get; init; }
115 |
116 | [JsonPropertyName("downloadUri")]
117 | public string? DownloadUri { get; init; }
118 |
119 | [JsonPropertyName("state")]
120 | public FileState? State { get; init; }
121 |
122 | [JsonPropertyName("source")]
123 | public FileSource? Source { get; init; }
124 |
125 | [JsonPropertyName("videoMetadata")]
126 | public VideoFileMetadata? VideoMetadata { get; init; }
127 | }
128 |
129 | public record VideoFileMetadata
130 | {
131 | [JsonPropertyName("videoDuration")]
132 | public string? VideoDuration { get; init; }
133 | }
134 |
135 | public enum FileState : byte
136 | {
137 | [JsonStringEnumMemberName("STATE_UNSPECIFIED")]
138 | Unspecified,
139 |
140 | [JsonStringEnumMemberName("PROCESSING")]
141 | Processing,
142 |
143 | [JsonStringEnumMemberName("ACTIVE")]
144 | Active,
145 |
146 | [JsonStringEnumMemberName("FAILED")]
147 | Failed,
148 | }
149 |
150 | public enum FileSource : byte
151 | {
152 | [JsonStringEnumMemberName("SOURCE_UNSPECIFIED")]
153 | Unspecified,
154 |
155 | [JsonStringEnumMemberName("UPLOADED")]
156 | Uploaded,
157 |
158 | [JsonStringEnumMemberName("GENERATED")]
159 | Generated,
160 | }
161 |
162 | public record FileStatus
163 | {
164 | [JsonPropertyName("code")]
165 | public int? Code { get; init; }
166 |
167 | [JsonPropertyName("message")]
168 | public string? Message { get; init; }
169 |
170 | [JsonPropertyName("details")]
171 | public Dictionary[]? Details { get; init; }
172 | }
--------------------------------------------------------------------------------
/src/GemiNet/Models/GeneratingContent.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace GemiNet;
4 |
5 | public record GenerateContentRequest
6 | {
7 | [JsonPropertyName("model")]
8 | public required string Model { get; init; }
9 |
10 | [JsonPropertyName("contents")]
11 | public required Contents Contents { get; init; }
12 |
13 | [JsonPropertyName("tools")]
14 | public Tool[]? Tools { get; init; }
15 |
16 | [JsonPropertyName("toolConfig")]
17 | public ToolConfig? ToolConfig { get; init; }
18 |
19 | [JsonPropertyName("safetySettings")]
20 | public SafetySetting[]? SafetySettings { get; init; }
21 |
22 | [JsonPropertyName("systemInstruction")]
23 | public Content? SystemInstruction { get; init; }
24 |
25 | [JsonPropertyName("generationConfig")]
26 | public GenerationConfig? GenerationConfig { get; init; }
27 | }
28 |
29 | public record GenerateContentResponse
30 | {
31 | [JsonPropertyName("candidates")]
32 | public Candidate[]? Candidates { get; init; }
33 |
34 | [JsonPropertyName("promptFeedback")]
35 | public GenerateContentResponsePromptFeedback? PromptFeedback { get; init; }
36 |
37 | [JsonPropertyName("usageMetadata")]
38 | public GenerateContentResponseUsageMetadata? UsageMetadata { get; init; }
39 |
40 | [JsonPropertyName("modelVersion")]
41 | public string? ModelVersion { get; init; }
42 | }
43 |
44 | public record GenerateContentResponsePromptFeedback
45 | {
46 | [JsonPropertyName("blockReason")]
47 | public BlockReason? BlockReason { get; init; }
48 |
49 | [JsonPropertyName("safetyRatings")]
50 | public SafetyRating[]? SafetyRatings { get; init; }
51 | }
52 |
53 | public enum BlockReason
54 | {
55 | [JsonStringEnumMemberName("BLOCK_REASON_UNSPECIFIED")]
56 | Unspecified,
57 |
58 | [JsonStringEnumMemberName("SAFETY")]
59 | Safety,
60 |
61 | [JsonStringEnumMemberName("OTHER")]
62 | Other,
63 |
64 | [JsonStringEnumMemberName("BLOCKLIST")]
65 | BlockList,
66 |
67 | [JsonStringEnumMemberName("PROHIBITED_CONTENT")]
68 | ProhibitedContent,
69 |
70 | [JsonStringEnumMemberName("IMAGE_SAFETY")]
71 | ImageSafety,
72 | }
73 |
74 | public record GenerateContentResponseUsageMetadata
75 | {
76 | [JsonPropertyName("promptTokenCount")]
77 | public int? PromptTokenCount { get; init; }
78 |
79 | [JsonPropertyName("cachedContentTokenCount")]
80 | public int? CachedContentTokenCount { get; init; }
81 |
82 | [JsonPropertyName("candidatesTokenCount")]
83 | public int? CandidatesTokenCount { get; init; }
84 |
85 | [JsonPropertyName("toolUsePromptTokenCount")]
86 | public int? ToolUsePromptTokenCount { get; init; }
87 |
88 | [JsonPropertyName("thoughtsTokenCount")]
89 | public int? ThoughtsTokenCount { get; init; }
90 |
91 | [JsonPropertyName("totalTokenCount")]
92 | public int? TotalTokenCount { get; init; }
93 |
94 | [JsonPropertyName("promptTokensDetails")]
95 | public ModalityTokenCount[]? PromptTokensDetails { get; init; }
96 |
97 | [JsonPropertyName("cacheTokensDetails")]
98 | public ModalityTokenCount[]? CacheTokensDetails { get; init; }
99 |
100 | [JsonPropertyName("toolUsePromptTokensDetails")]
101 | public ModalityTokenCount[]? ToolUsePromptTokensDetails { get; init; }
102 | }
103 |
104 | public record Candidate
105 | {
106 | [JsonPropertyName("content")]
107 | public Content? Content { get; init; }
108 |
109 | [JsonPropertyName("finishReason")]
110 | public FinishReason? FinishReason { get; init; }
111 |
112 | [JsonPropertyName("safetyRatings")]
113 | public SafetyRating[]? SafetyRatings { get; init; }
114 |
115 | [JsonPropertyName("citationMetadata")]
116 | public CitationMetadata? CitationMetadata { get; init; }
117 |
118 | [JsonPropertyName("tokenCount")]
119 | public int? TokenCount { get; init; }
120 |
121 | [JsonPropertyName("groundingAttributions")]
122 | public GroundingAttribution[]? GroundingAttributions { get; init; }
123 |
124 | [JsonPropertyName("groundingMetadata")]
125 | public GroundingAttribution? GroundingAttribution { get; init; }
126 |
127 | [JsonPropertyName("avgLogprobs")]
128 | public double? AvgLogprobs { get; init; }
129 |
130 | [JsonPropertyName("logprobsResult")]
131 | public LogprobsResult? LogprobsResult { get; init; }
132 |
133 | [JsonPropertyName("urlRetrievalMetadata")]
134 | public UrlRetrievalMetadata? UrlRetrievalMetadata { get; init; }
135 |
136 | [JsonPropertyName("index")]
137 | public int? Index { get; init; }
138 | }
139 |
140 | public enum FinishReason
141 | {
142 | [JsonStringEnumMemberName("FINISH_REASON_UNSPECIFIED")]
143 | Unspecified,
144 |
145 | [JsonStringEnumMemberName("STOP")]
146 | Stop,
147 |
148 | [JsonStringEnumMemberName("MAX_TOKENS")]
149 | MaxTokens,
150 |
151 | [JsonStringEnumMemberName("SAFETY")]
152 | Safety,
153 |
154 | [JsonStringEnumMemberName("RECITATION")]
155 | Recitation,
156 |
157 | [JsonStringEnumMemberName("LANGUAGE")]
158 | Language,
159 |
160 | [JsonStringEnumMemberName("OTHER")]
161 | Other,
162 |
163 | [JsonStringEnumMemberName("BLOCKLIST")]
164 | BlockList,
165 |
166 | [JsonStringEnumMemberName("PROHIBITED_CONTENT")]
167 | ProhibitedContent,
168 |
169 | [JsonStringEnumMemberName("SPII")]
170 | Spii,
171 |
172 | [JsonStringEnumMemberName("MALFORMED_FUNCTION_CALL")]
173 | MalformedFunctionCall,
174 |
175 | [JsonStringEnumMemberName("IMAGE_SAFETY")]
176 | ImageSafety,
177 | }
178 |
179 | public record GroundingAttribution
180 | {
181 | [JsonPropertyName("sourceId")]
182 | public required AttributionSourceId SourceId { get; init; }
183 |
184 | [JsonPropertyName("content")]
185 | public required Content Content { get; init; }
186 | }
187 |
188 | public record AttributionSourceId
189 | {
190 | [JsonPropertyName("groundingPassage")]
191 | public GroundingPassageId? GroundingPassage { get; init; }
192 |
193 | [JsonPropertyName("semanticRetrieverChunk")]
194 | public SemanticRetrieverChunk? SemanticRetrieverChunk { get; init; }
195 | }
196 |
197 | public record GroundingPassageId
198 | {
199 | [JsonPropertyName("passageId")]
200 | public required string PassageId { get; init; }
201 |
202 | [JsonPropertyName("partIndex")]
203 | public required int PartIndex { get; init; }
204 | }
205 |
206 | public record SemanticRetrieverChunk
207 | {
208 | [JsonPropertyName("source")]
209 | public required string Source { get; init; }
210 |
211 | [JsonPropertyName("chunk")]
212 | public required string Chunk { get; init; }
213 | }
214 |
215 | public record GroundingMetadata
216 | {
217 | [JsonPropertyName("groundingChunks")]
218 | public required GroundingChunk[] GroundingChunks { get; init; }
219 |
220 | [JsonPropertyName("groundingSupports")]
221 | public required GroundingSupport[] GroundingSupports { get; init; }
222 |
223 | [JsonPropertyName("webSearchQueries")]
224 | public required string[] WebSearchQueries { get; init; }
225 |
226 | [JsonPropertyName("searchEntryPoint")]
227 | public SearchEntryPoint? SearchEntryPoint { get; init; }
228 |
229 | [JsonPropertyName("retrievalMetadata")]
230 | public required RetrievalMetadata RetrievalMetadata { get; init; }
231 | }
232 |
233 | public record SearchEntryPoint
234 | {
235 | [JsonPropertyName("renderedContent")]
236 | public string? RenderedContent { get; init; }
237 |
238 | [JsonPropertyName("sdkBlob")]
239 | public string? SdkBlob { get; init; }
240 | }
241 |
242 | public record GroundingChunk
243 | {
244 | [JsonPropertyName("web")]
245 | public Web? Web { get; init; }
246 | }
247 |
248 | public record Web
249 | {
250 | [JsonPropertyName("uri")]
251 | public required string Uri { get; init; }
252 |
253 | [JsonPropertyName("title")]
254 | public required string Title { get; init; }
255 | }
256 |
257 | public record GroundingSupport
258 | {
259 | [JsonPropertyName("groundingChunkIndices")]
260 | public required int[] GroundingChunkIndices { get; init; }
261 |
262 | [JsonPropertyName("confidenceScores")]
263 | public required double[] ConfidenceScores { get; init; }
264 |
265 | [JsonPropertyName("segment")]
266 | public required Segment Segment { get; init; }
267 | }
268 |
269 | public record Segment
270 | {
271 | [JsonPropertyName("partIndex")]
272 | public required int PartIndex { get; init; }
273 |
274 | [JsonPropertyName("startIndex")]
275 | public required int StartIndex { get; init; }
276 |
277 | [JsonPropertyName("endIndex")]
278 | public required int EndIndex { get; init; }
279 |
280 | [JsonPropertyName("text")]
281 | public required string Text { get; init; }
282 | }
283 |
284 | public record RetrievalMetadata
285 | {
286 | [JsonPropertyName("googleSearchDynamicRetrievalScore")]
287 | public double? GoogleSearchDynamicRetrievalScore { get; init; }
288 | }
289 |
290 | public record LogprobsResult
291 | {
292 | [JsonPropertyName("topCandidates")]
293 | public required LogprobsResultTopCandidates[] TopCandidates { get; init; }
294 |
295 | [JsonPropertyName("chosenCandidates")]
296 | public required LogprobsResultCandidate[] ChosenCandidates { get; init; }
297 | }
298 |
299 | public record LogprobsResultTopCandidates
300 | {
301 | [JsonPropertyName("candidates")]
302 | public required LogprobsResultCandidate[] Candidates { get; init; }
303 | }
304 |
305 | public record LogprobsResultCandidate
306 | {
307 | [JsonPropertyName("token")]
308 | public required string Token { get; init; }
309 |
310 | [JsonPropertyName("tokenId")]
311 | public required int TokenId { get; init; }
312 |
313 | [JsonPropertyName("logProbability")]
314 | public required double LogProbability { get; init; }
315 | }
316 |
317 | public record UrlRetrievalMetadata
318 | {
319 | [JsonPropertyName("urlRetrievalContexts")]
320 | public required UrlRetrievalContext[] UrlRetrievalContexts { get; init; }
321 | }
322 |
323 | public record UrlRetrievalContext
324 | {
325 | [JsonPropertyName("retrievedUrl")]
326 | public required string RetrievedUrl { get; init; }
327 | }
328 |
329 | public record CitationMetadata
330 | {
331 | [JsonPropertyName("citationSources")]
332 | public required CitationSource[] CitationSources { get; init; }
333 | }
334 |
335 | public record CitationSource
336 | {
337 | [JsonPropertyName("startIndex")]
338 | public int? StartIndex { get; init; }
339 |
340 | [JsonPropertyName("endIndex")]
341 | public int? EndIndex { get; init; }
342 |
343 | [JsonPropertyName("uri")]
344 | public string? Uri { get; init; }
345 |
346 | [JsonPropertyName("license")]
347 | public string? License { get; init; }
348 | }
349 |
350 | public record GenerationConfig
351 | {
352 | [JsonPropertyName("stopSequences")]
353 | public string[]? StopSequences { get; init; }
354 |
355 | [JsonPropertyName("responseMimeType")]
356 | public string? ResponseMimeType { get; init; }
357 |
358 | [JsonPropertyName("responseSchema")]
359 | public Schema? ResponseSchema { get; init; }
360 |
361 | [JsonPropertyName("responseModalities")]
362 | public Modality[]? ResponseModalities { get; init; }
363 |
364 | [JsonPropertyName("candidateCount")]
365 | public int? CandidateCount { get; init; }
366 |
367 | [JsonPropertyName("maxOutputTokens")]
368 | public int? MaxOutputTokens { get; init; }
369 |
370 | [JsonPropertyName("temperature")]
371 | public double? Temperature { get; init; }
372 |
373 | [JsonPropertyName("topP")]
374 | public double? TopP { get; init; }
375 |
376 | [JsonPropertyName("topK")]
377 | public int? TopK { get; init; }
378 |
379 | [JsonPropertyName("seed")]
380 | public int? Seed { get; init; }
381 |
382 | [JsonPropertyName("presencePenalty")]
383 | public double? PresencePenalty { get; init; }
384 |
385 | [JsonPropertyName("frequencyPenalty")]
386 | public double? FrequencyPenalty { get; init; }
387 |
388 | [JsonPropertyName("responseLogprobs")]
389 | public bool? ResponseLogprobs { get; init; }
390 |
391 | [JsonPropertyName("logprobs")]
392 | public int? Logprobs { get; init; }
393 |
394 | [JsonPropertyName("enableEnhancedCivicAnswers")]
395 | public bool? EnableEnhancedCivicAnswers { get; init; }
396 |
397 | [JsonPropertyName("speechConfig")]
398 | public SpeechConfig? SpeechConfig { get; init; }
399 |
400 | [JsonPropertyName("thinkingConfig")]
401 | public ThinkingConfig? ThinkingConfig { get; init; }
402 |
403 | [JsonPropertyName("mediaResolution")]
404 | public MediaResolution? MediaResolution { get; init; }
405 | }
406 |
407 | public enum Modality
408 | {
409 | [JsonStringEnumMemberName("MODALITY_UNSPECIFIED")]
410 | Unspecified,
411 |
412 | [JsonStringEnumMemberName("TEXT")]
413 | Text,
414 |
415 | [JsonStringEnumMemberName("IMAGE")]
416 | Image,
417 |
418 | [JsonStringEnumMemberName("AUDIO")]
419 | Audio,
420 | }
421 |
422 | public record ModalityTokenCount
423 | {
424 | [JsonPropertyName("modality")]
425 | public required Modality Modality { get; init; }
426 |
427 | [JsonPropertyName("tokenCount")]
428 | public required int TokenCount { get; init; }
429 | }
430 |
431 | public record SpeechConfig
432 | {
433 | [JsonPropertyName("voiceConfig")]
434 | public required VoiceConfig VoiceConfig { get; init; }
435 |
436 | [JsonPropertyName("languageCode")]
437 | public string? LanguageCode { get; init; }
438 | }
439 |
440 | public record VoiceConfig
441 | {
442 | [JsonPropertyName("prebuiltVoiceConfig")]
443 | public required PrebuiltVoiceConfig PrebuiltVoiceConfig { get; init; }
444 | }
445 |
446 | public record PrebuiltVoiceConfig
447 | {
448 | [JsonPropertyName("voiceName")]
449 | public required string VoiceName { get; init; }
450 | }
451 |
452 | public record ThinkingConfig
453 | {
454 | [JsonPropertyName("includeThoughts")]
455 | public required bool IncludeThoughts { get; init; }
456 |
457 | [JsonPropertyName("thinkingBudget")]
458 | public required int ThinkingBudget { get; init; }
459 | }
460 |
461 | public enum MediaResolution
462 | {
463 | [JsonStringEnumMemberName("MEDIA_RESOLUTION_UNSPECIFIED")]
464 | Unspecified,
465 |
466 | [JsonStringEnumMemberName("MEDIA_RESOLUTION_LOW")]
467 | Low,
468 |
469 | [JsonStringEnumMemberName("MEDIA_RESOLUTION_MEDIUM")]
470 | Medium,
471 |
472 | [JsonStringEnumMemberName("MEDIA_RESOLUTION_HIGH")]
473 | High,
474 | }
475 |
476 | public enum HarmCategory
477 | {
478 | [JsonStringEnumMemberName("HARM_CATEGORY_UNSPECIFIED")]
479 | Unspecified,
480 |
481 | [JsonStringEnumMemberName("HARM_CATEGORY_DEROGATORY")]
482 | Derogatory,
483 |
484 | [JsonStringEnumMemberName("HARM_CATEGORY_TOXICITY")]
485 | Toxicity,
486 |
487 | [JsonStringEnumMemberName("HARM_CATEGORY_VIOLENCE")]
488 | Violence,
489 |
490 | [JsonStringEnumMemberName("HARM_CATEGORY_SEXUAL")]
491 | Sexual,
492 |
493 | [JsonStringEnumMemberName("HARM_CATEGORY_MEDICAL")]
494 | Medical,
495 |
496 | [JsonStringEnumMemberName("HARM_CATEGORY_DANGEROUS")]
497 | Dangerous,
498 |
499 | [JsonStringEnumMemberName("HARM_CATEGORY_HARASSMENT")]
500 | Harassment,
501 |
502 | [JsonStringEnumMemberName("HARM_CATEGORY_HATE_SPEECH")]
503 | HateSpeech,
504 |
505 | [JsonStringEnumMemberName("HARM_CATEGORY_SEXUALLY_EXPLICIT")]
506 | SexuallyExplicit,
507 |
508 | [JsonStringEnumMemberName("HARM_CATEGORY_DANGEROUS_CONTENT")]
509 | DangerousContent,
510 |
511 | [JsonStringEnumMemberName("HARM_CATEGORY_CIVIC_INTEGRITY")]
512 | CivicIntegrity,
513 | }
514 |
515 | public record SafetyRating
516 | {
517 | [JsonPropertyName("category")]
518 | public required HarmCategory Category { get; init; }
519 |
520 | [JsonPropertyName("probability")]
521 | public required HarmProbability Probability { get; init; }
522 |
523 | [JsonPropertyName("blocked")]
524 | public bool? Blocked { get; init; }
525 | }
526 |
527 | public enum HarmProbability
528 | {
529 | [JsonStringEnumMemberName("HARM_PROBABILITY_UNSPECIFIED")]
530 | Unspecified,
531 |
532 | [JsonStringEnumMemberName("NEGLIGIBLE")]
533 | Negligible,
534 |
535 | [JsonStringEnumMemberName("LOW")]
536 | Low,
537 |
538 | [JsonStringEnumMemberName("MEDIUM")]
539 | Medium,
540 |
541 | [JsonStringEnumMemberName("HIGH")]
542 | High,
543 | }
544 |
545 | public record SafetySetting
546 | {
547 | [JsonPropertyName("category")]
548 | public required HarmCategory Category { get; init; }
549 |
550 | [JsonPropertyName("threshold")]
551 | public required HarmBlockThreshold Threshold { get; init; }
552 | }
553 |
554 | public enum HarmBlockThreshold
555 | {
556 | [JsonStringEnumMemberName("HARM_BLOCK_THRESHOLD_UNSPECIFIED")]
557 | Unspecified,
558 |
559 | [JsonStringEnumMemberName("BLOCK_LOW_AND_ABOVE")]
560 | BlockLowAndAbove,
561 |
562 | [JsonStringEnumMemberName("BLOCK_MEDIUM_AND_ABOVE")]
563 | BlockMediumAndAbove,
564 |
565 | [JsonStringEnumMemberName("BLOCK_ONLY_HIGH")]
566 | BlockOnlyHigh,
567 |
568 | [JsonStringEnumMemberName("BLOCK_NONE")]
569 | BlockNone,
570 |
571 | [JsonStringEnumMemberName("OFF")]
572 | Off,
573 | }
574 |
575 |
--------------------------------------------------------------------------------
/src/GemiNet/Models/Live.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace GemiNet;
4 |
5 | public record BidiGenerateContent
6 | {
7 | [JsonPropertyName("setup")]
8 | public BidiGenerateContentSetup? Setup { get; init; }
9 |
10 | [JsonPropertyName("clientContent")]
11 | public BidiGenerateContentClientContent? ClientContent { get; init; }
12 |
13 | [JsonPropertyName("realtimeInput")]
14 | public BidiGenerateContentRealtimeInput? RealtimeInput { get; init; }
15 |
16 | [JsonPropertyName("toolResponse")]
17 | public BidiGenerateContentToolResponse? ToolResponse { get; init; }
18 | }
19 |
20 | public record ActivityEnd;
21 |
22 | public enum ActivityHandling
23 | {
24 | [JsonStringEnumMemberName("ACTIVITY_HANDLING_UNSPECIFIED")]
25 | Unspecified,
26 |
27 | [JsonStringEnumMemberName("START_OF_ACTIVITY_INTERRUPTS")]
28 | StartOfActivityInterrupts,
29 |
30 | [JsonStringEnumMemberName("NO_INTERRUPTION")]
31 | NoInterruption,
32 | }
33 |
34 | public record ActivityStart;
35 |
36 | public record AudioTranscriptionConfig;
37 |
38 | public record AutomaticActivityDetection
39 | {
40 | [JsonPropertyName("disabled")]
41 | public bool? Disabled { get; init; }
42 |
43 | [JsonPropertyName("startOfSpeechSensitivity")]
44 | public StartSensitivity? StartOfSpeechSensitivity { get; init; }
45 |
46 | [JsonPropertyName("prefixPaddingMs")]
47 | public int? PrefixPaddingMs { get; init; }
48 |
49 | [JsonPropertyName("endOfSpeechSensitivity")]
50 | public EndSensitivity? EndOfSpeechSensitivity { get; init; }
51 |
52 | [JsonPropertyName("silenceDurationMs")]
53 | public int? SilenceDurationMs { get; init; }
54 | }
55 |
56 | public record BidiGenerateContentClientContent
57 | {
58 | [JsonPropertyName("turns")]
59 | public Contents? Turns { get; init; }
60 |
61 | [JsonPropertyName("turnComplete")]
62 | public bool? TurnComplete { get; init; }
63 | }
64 |
65 | public record BidiGenerateContentRealtimeInput
66 | {
67 | [JsonPropertyName("mediaChunks")]
68 | public Blob[]? MediaChunks { get; init; }
69 |
70 | [JsonPropertyName("audio")]
71 | public Blob? Audio { get; init; }
72 |
73 | [JsonPropertyName("video")]
74 | public Blob? Video { get; init; }
75 |
76 | [JsonPropertyName("activityStart")]
77 | public ActivityStart? ActivityStart { get; init; }
78 |
79 | [JsonPropertyName("activityEnd")]
80 | public ActivityEnd? ActivityEnd { get; init; }
81 |
82 | [JsonPropertyName("audioStreamEnd")]
83 | public bool? AudioStreamEnd { get; init; }
84 |
85 | [JsonPropertyName("text")]
86 | public string? Text { get; init; }
87 | }
88 |
89 | public record BidiGenerateContentServerContent
90 | {
91 | [JsonPropertyName("generationComplete")]
92 | public bool? GenerationComplete { get; init; }
93 |
94 | [JsonPropertyName("turnComplete")]
95 | public bool? TurnComplete { get; init; }
96 |
97 | [JsonPropertyName("interrupted")]
98 | public bool? Interrupted { get; init; }
99 |
100 | [JsonPropertyName("groundingMetadata")]
101 | public GroundingMetadata? GroundingMetadata { get; init; }
102 |
103 | [JsonPropertyName("inputTranscription")]
104 | public BidiGenerateContentTranscription? InputTranscription { get; init; }
105 |
106 | [JsonPropertyName("outputTranscription")]
107 | public BidiGenerateContentTranscription? OutputTranscription { get; init; }
108 |
109 | [JsonPropertyName("urlContextMetadata")]
110 | public UrlContextMetadata? UrlContextMetadata { get; init; }
111 |
112 | [JsonPropertyName("modelTurn")]
113 | public Content? ModelTurn { get; init; }
114 | }
115 |
116 | public record BidiGenerateContentServerMessage
117 | {
118 | [JsonPropertyName("usageMetadata")]
119 | public LiveUsageMetadata? UsageMetadata { get; init; }
120 |
121 | [JsonPropertyName("setupComplete")]
122 | public BidiGenerateContentSetupComplete? SetupComplete { get; init; }
123 |
124 | [JsonPropertyName("serverContent")]
125 | public BidiGenerateContentServerContent? ServerContent { get; init; }
126 |
127 | [JsonPropertyName("toolCall")]
128 | public BidiGenerateContentToolCall? ToolCall { get; init; }
129 |
130 | [JsonPropertyName("toolCallCancellation")]
131 | public BidiGenerateContentToolCallCancellation? ToolCallCancellation { get; init; }
132 |
133 | [JsonPropertyName("goAway")]
134 | public GoAway? GoAway { get; init; }
135 |
136 | [JsonPropertyName("sessionResumptionUpdate")]
137 | public SessionResumptionUpdate? SessionResumptionUpdate { get; init; }
138 | }
139 |
140 | public record BidiGenerateContentSetup
141 | {
142 | [JsonPropertyName("model")]
143 | public required string Model { get; init; }
144 |
145 | [JsonPropertyName("generationConfig")]
146 | public GenerationConfig? Config { get; init; }
147 |
148 | [JsonPropertyName("systemInstruction")]
149 | public Content? SystemInstruction { get; init; }
150 |
151 | [JsonPropertyName("tools")]
152 | public Tool[]? Tools { get; init; }
153 |
154 | [JsonPropertyName("realtimeInputConfig")]
155 | public RealtimeInputConfig? RealtimeInputConfig { get; init; }
156 |
157 | [JsonPropertyName("sessionResumption")]
158 | public SessionResumptionConfig? SessionResumption { get; init; }
159 |
160 | [JsonPropertyName("contextWindowCompression")]
161 | public ContextWindowCompressionConfig? ContextWindowCompression { get; init; }
162 |
163 | [JsonPropertyName("inputAudioTranscription")]
164 | public AudioTranscriptionConfig? InputAudioTranscription { get; init; }
165 |
166 | [JsonPropertyName("outputAudioTranscription")]
167 | public AudioTranscriptionConfig? OutputAudioTranscription { get; init; }
168 |
169 | [JsonPropertyName("proactivity")]
170 | public ProactivityConfig? Proactivity { get; init; }
171 | }
172 |
173 | public record BidiGenerateContentSetupComplete;
174 |
175 | public record BidiGenerateContentToolCall
176 | {
177 | [JsonPropertyName("functionCalls")]
178 | public FunctionCall[]? FunctionCalls { get; init; }
179 | }
180 |
181 | public record BidiGenerateContentToolCallCancellation
182 | {
183 | [JsonPropertyName("ids")]
184 | public string[]? Ids { get; init; }
185 | }
186 |
187 | public record BidiGenerateContentToolResponse
188 | {
189 | [JsonPropertyName("functionResponses")]
190 | public FunctionResponse[]? FunctionResponses { get; init; }
191 | }
192 |
193 | public record BidiGenerateContentTranscription
194 | {
195 | [JsonPropertyName("text")]
196 | public required string Text { get; init; }
197 | }
198 |
199 | public record ContextWindowCompressionConfig
200 | {
201 | [JsonPropertyName("slidingWindow")]
202 | public SlidingWindow? SlidingWindow { get; init; }
203 |
204 | [JsonPropertyName("triggerTokens")]
205 | public long? TriggerTokens { get; init; }
206 | }
207 |
208 | public enum EndSensitivity
209 | {
210 | [JsonStringEnumMemberName("END_SENSITIVITY_UNSPECIFIED")]
211 | Unspecified,
212 |
213 | [JsonStringEnumMemberName("END_SENSITIVITY_HIGH")]
214 | High,
215 |
216 | [JsonStringEnumMemberName("END_SENSITIVITY_LOW")]
217 | Low,
218 | }
219 |
220 | public record GoAway
221 | {
222 | [JsonPropertyName("timeLeft")]
223 | public string? TimeLeft { get; init; }
224 | }
225 |
226 | public record ProactivityConfig
227 | {
228 | [JsonPropertyName("proactiveAudio")]
229 | public bool? ProactiveAudio { get; init; }
230 | }
231 |
232 | public record RealtimeInputConfig
233 | {
234 | [JsonPropertyName("automaticActivityDetection")]
235 | public AutomaticActivityDetection? AutomaticActivityDetection { get; init; }
236 |
237 | [JsonPropertyName("activityHandling")]
238 | public ActivityHandling? ActivityHandling { get; init; }
239 |
240 | [JsonPropertyName("turnCoverage")]
241 | public TurnCoverage? TurnCoverage { get; init; }
242 | }
243 |
244 | public record SessionResumptionConfig
245 | {
246 | [JsonPropertyName("handle")]
247 | public string? Handle { get; init; }
248 | }
249 |
250 | public record SessionResumptionUpdate
251 | {
252 | [JsonPropertyName("newHandle")]
253 | public string? NewHandle { get; init; }
254 |
255 | [JsonPropertyName("resumable")]
256 | public bool? Resumable { get; init; }
257 | }
258 |
259 | public record SlidingWindow
260 | {
261 | [JsonPropertyName("targetTokens")]
262 | public required long TargetTokens { get; init; }
263 | }
264 |
265 | public enum StartSensitivity
266 | {
267 | [JsonStringEnumMemberName("START_SENSITIVITY_UNSPECIFIED")]
268 | Unspecified,
269 |
270 | [JsonStringEnumMemberName("START_SENSITIVITY_HIGH")]
271 | High,
272 |
273 | [JsonStringEnumMemberName("START_SENSITIVITY_LOW")]
274 | Low,
275 | }
276 |
277 | public enum TurnCoverage
278 | {
279 | [JsonStringEnumMemberName("TURN_COVERAGE_UNSPECIFIED")]
280 | Unspecified,
281 |
282 | [JsonStringEnumMemberName("TURN_INCLUDES_ONLY_ACTIVITY")]
283 | IncludesOnlyActivity,
284 |
285 | [JsonStringEnumMemberName("TURN_INCLUDES_ALL_INPUT")]
286 | IncludesAllInput,
287 | }
288 |
289 | public record UrlContextMetadata
290 | {
291 | [JsonPropertyName("urlMetadata")]
292 | public required UrlMetadata UrlMetadata { get; init; }
293 | }
294 |
295 | public record UrlMetadata
296 | {
297 | [JsonPropertyName("retrievedUrl")]
298 | public string? RetrievedUrl { get; init; }
299 |
300 | [JsonPropertyName("urlRetrievalStatus")]
301 | public UrlRetrievalStatus? UrlRetrievalStatus { get; init; }
302 | }
303 |
304 | public enum UrlRetrievalStatus
305 | {
306 | [JsonStringEnumMemberName("URL_RETRIEVAL_STATUS_UNSPECIFIED")]
307 | Unspecified,
308 |
309 | [JsonStringEnumMemberName("URL_RETRIEVAL_STATUS_SUCCESS")]
310 | Success,
311 |
312 | [JsonStringEnumMemberName("URL_RETRIEVAL_STATUS_ERROR")]
313 | Error,
314 | }
315 |
316 | public record LiveUsageMetadata
317 | {
318 | [JsonPropertyName("promptTokenCount")]
319 | public int? PromptTokenCount { get; init; }
320 |
321 | [JsonPropertyName("cachedContentTokenCount")]
322 | public int? CachedContentTokenCount { get; init; }
323 |
324 | [JsonPropertyName("responseTokenCount")]
325 | public int? ResponseTokenCount { get; init; }
326 |
327 | [JsonPropertyName("toolUsePromptTokenCount")]
328 | public int? ToolUsePromptTokenCount { get; init; }
329 |
330 | [JsonPropertyName("thoughtsTokenCount")]
331 | public int? ThoughtsTokenCount { get; init; }
332 |
333 | [JsonPropertyName("totalTokenCount")]
334 | public int? TotalTokenCount { get; init; }
335 |
336 | [JsonPropertyName("promptTokensDetails")]
337 | public ModalityTokenCount[]? PromptTokensDetails { get; init; }
338 |
339 | [JsonPropertyName("cacheTokensDetails")]
340 | public ModalityTokenCount[]? CacheTokensDetails { get; init; }
341 |
342 | [JsonPropertyName("responseTokensDetails")]
343 | public ModalityTokenCount[]? ResponseTokensDetails { get; init; }
344 |
345 | [JsonPropertyName("toolUsePromptTokensDetails")]
346 | public ModalityTokenCount[]? ToolUsePromptTokensDetails { get; init; }
347 | }
348 |
349 | public record CreateAuthTokenRequest
350 | {
351 | [JsonPropertyName("authToken")]
352 | public required AuthToken AuthToken { get; init; }
353 | }
354 |
355 | public record AuthToken
356 | {
357 | [JsonPropertyName("name")]
358 | public string? Name { get; init; }
359 |
360 | [JsonPropertyName("expireTime")]
361 | public string? ExpireTime { get; init; }
362 |
363 | [JsonPropertyName("newSessionExpireTime")]
364 | public string? NewSessionExpireTime { get; init; }
365 |
366 | [JsonPropertyName("bidiGenerateContentSetup")]
367 | public BidiGenerateContentSetup? BidiGenerateContentSetup { get; init; }
368 |
369 | [JsonPropertyName("uses")]
370 | public int? Uses { get; init; }
371 | }
--------------------------------------------------------------------------------
/src/GemiNet/Models/Models.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace GemiNet;
4 |
5 | public record Model
6 | {
7 | [JsonPropertyName("name")]
8 | public required string Name { get; init; }
9 |
10 | [JsonPropertyName("baseModelId")]
11 | public string? BaseModelId { get; init; }
12 |
13 | [JsonPropertyName("version")]
14 | public required string Version { get; init; }
15 |
16 | [JsonPropertyName("displayName")]
17 | public string? DisplayName { get; init; }
18 |
19 | [JsonPropertyName("description")]
20 | public string? Description { get; init; }
21 |
22 | [JsonPropertyName("inputTokenLimit")]
23 | public int? InputTokenLimit { get; init; }
24 |
25 | [JsonPropertyName("outputTokenLimit")]
26 | public int? OutputTokenLimit { get; init; }
27 |
28 | [JsonPropertyName("supportedGenerationMethods")]
29 | public string[]? SupportedGenerationMethods { get; init; }
30 |
31 | [JsonPropertyName("temperature")]
32 | public double? Temperature { get; init; }
33 |
34 | [JsonPropertyName("maxTemperature")]
35 | public double? MaxTemperature { get; init; }
36 |
37 | [JsonPropertyName("topP")]
38 | public double? TopP { get; init; }
39 |
40 | [JsonPropertyName("topK")]
41 | public int? TopK { get; init; }
42 | }
43 |
44 | public record ListModelsRequest
45 | {
46 | public int? PageSize { get; init; }
47 | public string? PageToken { get; init; }
48 | }
49 |
50 | public record ListModelsResponse
51 | {
52 | [JsonPropertyName("models")]
53 | public required Model[] Models { get; init; }
54 |
55 | [JsonPropertyName("nextPageToken")]
56 | public string? NextPageToken { get; init; }
57 | }
58 |
--------------------------------------------------------------------------------
/src/GemiNet/Models/Tokens.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace GemiNet;
4 |
5 | public record CountTokensRequest
6 | {
7 | [JsonPropertyName("model")]
8 | public required string Model { get; init; }
9 |
10 | [JsonPropertyName("contents")]
11 | public required Contents Contents { get; init; }
12 |
13 | [JsonPropertyName("generateContentRequest")]
14 | public GenerateContentRequest? GenerateContentRequest { get; init; }
15 | }
16 |
17 | public record CountTokensResponse
18 | {
19 | [JsonPropertyName("totalTokens")]
20 | public double? TotalTokens { get; init; }
21 |
22 | [JsonPropertyName("cachedContentTokenCount")]
23 | public double? CachedContentTokenCount { get; init; }
24 | }
--------------------------------------------------------------------------------