@tanstack/ai-gemini 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/esm/gemini-adapter.d.ts +71 -0
- package/dist/esm/gemini-adapter.js +375 -0
- package/dist/esm/gemini-adapter.js.map +1 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/message-types.d.ts +84 -0
- package/dist/esm/model-meta.d.ts +189 -0
- package/dist/esm/model-meta.js +43 -0
- package/dist/esm/model-meta.js.map +1 -0
- package/dist/esm/text/text-provider-options.d.ts +184 -0
- package/dist/esm/tools/code-execution-tool.d.ts +7 -0
- package/dist/esm/tools/code-execution-tool.js +9 -0
- package/dist/esm/tools/code-execution-tool.js.map +1 -0
- package/dist/esm/tools/computer-use-tool.d.ts +10 -0
- package/dist/esm/tools/computer-use-tool.js +13 -0
- package/dist/esm/tools/computer-use-tool.js.map +1 -0
- package/dist/esm/tools/file-search-tool.d.ts +7 -0
- package/dist/esm/tools/file-search-tool.js +10 -0
- package/dist/esm/tools/file-search-tool.js.map +1 -0
- package/dist/esm/tools/function-declaration-tool.d.ts +5 -0
- package/dist/esm/tools/google-maps-tool.d.ts +7 -0
- package/dist/esm/tools/google-maps-tool.js +10 -0
- package/dist/esm/tools/google-maps-tool.js.map +1 -0
- package/dist/esm/tools/google-search-retriveal-tool.d.ts +7 -0
- package/dist/esm/tools/google-search-retriveal-tool.js +10 -0
- package/dist/esm/tools/google-search-retriveal-tool.js.map +1 -0
- package/dist/esm/tools/google-search-tool.d.ts +7 -0
- package/dist/esm/tools/google-search-tool.js +10 -0
- package/dist/esm/tools/google-search-tool.js.map +1 -0
- package/dist/esm/tools/index.d.ts +9 -0
- package/dist/esm/tools/tool-converter.d.ts +22 -0
- package/dist/esm/tools/tool-converter.js +64 -0
- package/dist/esm/tools/tool-converter.js.map +1 -0
- package/dist/esm/tools/url-context-tool.d.ts +7 -0
- package/dist/esm/tools/url-context-tool.js +9 -0
- package/dist/esm/tools/url-context-tool.js.map +1 -0
- package/package.json +53 -0
- package/src/gemini-adapter.ts +577 -0
- package/src/index.ts +22 -0
- package/src/message-types.ts +125 -0
- package/src/model-meta.ts +887 -0
- package/src/text/text-provider-options.ts +246 -0
- package/src/tools/code-execution-tool.ts +17 -0
- package/src/tools/computer-use-tool.ts +25 -0
- package/src/tools/file-search-tool.ts +19 -0
- package/src/tools/function-declaration-tool.ts +34 -0
- package/src/tools/google-maps-tool.ts +19 -0
- package/src/tools/google-search-retriveal-tool.ts +21 -0
- package/src/tools/google-search-tool.ts +19 -0
- package/src/tools/index.ts +18 -0
- package/src/tools/tool-converter.ts +99 -0
- package/src/tools/url-context-tool.ts +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Tanner Linsley
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="./media/header_ai.png" >
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<br />
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
|
|
9
|
+
<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/TanStack/ai" target="\_parent">
|
|
12
|
+
<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
|
|
15
|
+
<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
|
|
16
|
+
</a>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div align="center">
|
|
20
|
+
<a href="#badge">
|
|
21
|
+
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
|
22
|
+
</a>
|
|
23
|
+
<a href="#badge">
|
|
24
|
+
<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
|
|
25
|
+
</a>
|
|
26
|
+
<a href="https://twitter.com/tan_stack">
|
|
27
|
+
<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
|
|
28
|
+
</a>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div align="center">
|
|
32
|
+
|
|
33
|
+
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
# TanStack AI
|
|
37
|
+
|
|
38
|
+
A powerful, type-safe AI SDK for building AI-powered applications.
|
|
39
|
+
|
|
40
|
+
- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
|
|
41
|
+
- **Multimodal content support** - Send images, audio, video, and documents
|
|
42
|
+
- Chat completion, streaming, and agent loop strategies
|
|
43
|
+
- Headless chat state management with adapters (SSE, HTTP stream, custom)
|
|
44
|
+
- Isomorphic type-safe tools with server/client execution
|
|
45
|
+
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions
|
|
46
|
+
|
|
47
|
+
### <a href="https://tanstack.com/ai">Read the docs →</b></a>
|
|
48
|
+
|
|
49
|
+
## Bonus: TanStack Start Integration
|
|
50
|
+
|
|
51
|
+
TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).
|
|
52
|
+
|
|
53
|
+
**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { createServerFnTool } from '@tanstack/ai-react'
|
|
57
|
+
|
|
58
|
+
// Define once, get AI tool AND server function (TanStack Start only)
|
|
59
|
+
const getProducts = createServerFnTool({
|
|
60
|
+
name: 'getProducts',
|
|
61
|
+
inputSchema: z.object({ query: z.string() }),
|
|
62
|
+
execute: async ({ query }) => db.products.search(query),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Use in AI chat
|
|
66
|
+
chat({ tools: [getProducts.server] })
|
|
67
|
+
|
|
68
|
+
// Call directly from components (no API endpoint needed!)
|
|
69
|
+
const products = await getProducts.serverFn({ query: 'laptop' })
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.
|
|
73
|
+
|
|
74
|
+
## Get Involved
|
|
75
|
+
|
|
76
|
+
- We welcome issues and pull requests!
|
|
77
|
+
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
|
|
78
|
+
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
|
|
79
|
+
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions
|
|
80
|
+
|
|
81
|
+
## Partners
|
|
82
|
+
|
|
83
|
+
<table align="center">
|
|
84
|
+
<tr>
|
|
85
|
+
<td>
|
|
86
|
+
<a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
|
|
87
|
+
<picture>
|
|
88
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
|
|
89
|
+
<source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
|
|
90
|
+
<img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
|
|
91
|
+
</picture>
|
|
92
|
+
</a>
|
|
93
|
+
</td>
|
|
94
|
+
<td>
|
|
95
|
+
<a href="https://www.cloudflare.com?utm_source=tanstack">
|
|
96
|
+
<picture>
|
|
97
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
|
|
98
|
+
<source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
|
|
99
|
+
<img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
|
|
100
|
+
</picture>
|
|
101
|
+
</a>
|
|
102
|
+
</td>
|
|
103
|
+
</tr>
|
|
104
|
+
</table>
|
|
105
|
+
|
|
106
|
+
<div align="center">
|
|
107
|
+
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
|
|
108
|
+
<p>
|
|
109
|
+
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
|
|
110
|
+
</p>
|
|
111
|
+
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
## Explore the TanStack Ecosystem
|
|
115
|
+
|
|
116
|
+
- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
|
|
117
|
+
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
|
|
118
|
+
- <a href="https://github.com/tanstack/devtools">TanStack Devtools</a> – Unified devtools panel
|
|
119
|
+
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
|
|
120
|
+
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
|
|
121
|
+
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
|
|
122
|
+
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
|
|
123
|
+
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
|
|
124
|
+
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
|
|
125
|
+
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
|
|
126
|
+
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
|
|
127
|
+
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering
|
|
128
|
+
|
|
129
|
+
… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>
|
|
130
|
+
|
|
131
|
+
<!-- USE THE FORCE LUKE -->
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BaseAdapter, AIAdapterConfig, ChatOptions, EmbeddingOptions, EmbeddingResult, StreamChunk, SummarizationOptions, SummarizationResult } from '@tanstack/ai';
|
|
2
|
+
import { GEMINI_EMBEDDING_MODELS, GEMINI_MODELS, GeminiChatModelProviderOptionsByName, GeminiModelInputModalitiesByName } from './model-meta.js';
|
|
3
|
+
import { ExternalTextProviderOptions } from './text/text-provider-options.js';
|
|
4
|
+
import { GeminiMessageMetadataByModality } from './message-types.js';
|
|
5
|
+
export interface GeminiAdapterConfig extends AIAdapterConfig {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Gemini-specific provider options
|
|
10
|
+
* Based on Google Generative AI SDK
|
|
11
|
+
* @see https://ai.google.dev/api/rest/v1/GenerationConfig
|
|
12
|
+
*/
|
|
13
|
+
export type GeminiProviderOptions = ExternalTextProviderOptions;
|
|
14
|
+
export declare class GeminiAdapter extends BaseAdapter<typeof GEMINI_MODELS, typeof GEMINI_EMBEDDING_MODELS, GeminiProviderOptions, Record<string, any>, GeminiChatModelProviderOptionsByName, GeminiModelInputModalitiesByName, GeminiMessageMetadataByModality> {
|
|
15
|
+
name: string;
|
|
16
|
+
models: readonly ["gemini-3-pro-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-preview-09-2025", "gemini-2.5-flash-lite", "gemini-2.5-flash-lite-preview-09-2025", "gemini-2.0-flash", "gemini-2.0-flash-lite"];
|
|
17
|
+
embeddingModels: readonly ["gemini-embedding-001"];
|
|
18
|
+
_modelProviderOptionsByName: GeminiChatModelProviderOptionsByName;
|
|
19
|
+
_modelInputModalitiesByName: GeminiModelInputModalitiesByName;
|
|
20
|
+
_messageMetadataByModality: GeminiMessageMetadataByModality;
|
|
21
|
+
private client;
|
|
22
|
+
constructor(config: GeminiAdapterConfig);
|
|
23
|
+
chatStream(options: ChatOptions<string, GeminiProviderOptions>): AsyncIterable<StreamChunk>;
|
|
24
|
+
summarize(options: SummarizationOptions): Promise<SummarizationResult>;
|
|
25
|
+
createEmbeddings(options: EmbeddingOptions): Promise<EmbeddingResult>;
|
|
26
|
+
private buildSummarizationPrompt;
|
|
27
|
+
private estimateTokens;
|
|
28
|
+
private processStreamChunks;
|
|
29
|
+
private convertContentPartToGemini;
|
|
30
|
+
private formatMessages;
|
|
31
|
+
/**
|
|
32
|
+
* Maps common options to Gemini-specific format
|
|
33
|
+
* Handles translation of normalized options to Gemini's API format
|
|
34
|
+
*/
|
|
35
|
+
private mapCommonOptionsToGemini;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates a Gemini adapter with simplified configuration
|
|
39
|
+
* @param apiKey - Your Google API key
|
|
40
|
+
* @returns A fully configured Gemini adapter instance
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const gemini = createGemini("AIza...");
|
|
45
|
+
*
|
|
46
|
+
* const ai = new AI({
|
|
47
|
+
* adapters: {
|
|
48
|
+
* gemini,
|
|
49
|
+
* }
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function createGemini(apiKey: string, config?: Omit<GeminiAdapterConfig, 'apiKey'>): GeminiAdapter;
|
|
54
|
+
/**
|
|
55
|
+
* Create a Gemini adapter with automatic API key detection from environment variables.
|
|
56
|
+
*
|
|
57
|
+
* Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:
|
|
58
|
+
* - `process.env` (Node.js)
|
|
59
|
+
* - `window.env` (Browser with injected env)
|
|
60
|
+
*
|
|
61
|
+
* @param config - Optional configuration (excluding apiKey which is auto-detected)
|
|
62
|
+
* @returns Configured Gemini adapter instance
|
|
63
|
+
* @throws Error if API key is not found in environment
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* // Automatically uses GOOGLE_API_KEY or GEMINI_API_KEY from environment
|
|
68
|
+
* const aiInstance = ai(gemini());
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function gemini(config?: Omit<GeminiAdapterConfig, 'apiKey'>): GeminiAdapter;
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { GoogleGenAI, FinishReason } from "@google/genai";
|
|
2
|
+
import { BaseAdapter } from "@tanstack/ai";
|
|
3
|
+
import { GEMINI_MODELS, GEMINI_EMBEDDING_MODELS } from "./model-meta.js";
|
|
4
|
+
import { convertToolsToProviderFormat } from "./tools/tool-converter.js";
|
|
5
|
+
class GeminiAdapter extends BaseAdapter {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
super(config);
|
|
8
|
+
this.name = "gemini";
|
|
9
|
+
this.models = GEMINI_MODELS;
|
|
10
|
+
this.embeddingModels = GEMINI_EMBEDDING_MODELS;
|
|
11
|
+
this.client = new GoogleGenAI({
|
|
12
|
+
apiKey: config.apiKey
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async *chatStream(options) {
|
|
16
|
+
const mappedOptions = this.mapCommonOptionsToGemini(options);
|
|
17
|
+
try {
|
|
18
|
+
const result = await this.client.models.generateContentStream(mappedOptions);
|
|
19
|
+
yield* this.processStreamChunks(result, options.model);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
const timestamp = Date.now();
|
|
22
|
+
yield {
|
|
23
|
+
type: "error",
|
|
24
|
+
id: this.generateId(),
|
|
25
|
+
model: options.model,
|
|
26
|
+
timestamp,
|
|
27
|
+
error: {
|
|
28
|
+
message: error instanceof Error ? error.message : "An unknown error occurred during the chat stream."
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async summarize(options) {
|
|
34
|
+
const prompt = this.buildSummarizationPrompt(options, options.text);
|
|
35
|
+
const result = await this.client.models.generateContent({
|
|
36
|
+
model: options.model,
|
|
37
|
+
contents: [{ role: "user", parts: [{ text: prompt }] }],
|
|
38
|
+
config: {
|
|
39
|
+
temperature: 0.3,
|
|
40
|
+
maxOutputTokens: options.maxLength || 500
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
let summary = "";
|
|
44
|
+
if (result.candidates?.[0]?.content?.parts) {
|
|
45
|
+
const parts = result.candidates[0].content.parts;
|
|
46
|
+
for (const part of parts) {
|
|
47
|
+
if (part.text) {
|
|
48
|
+
summary += part.text;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!summary && typeof result.text === "string") {
|
|
53
|
+
summary = result.text;
|
|
54
|
+
}
|
|
55
|
+
const promptTokens = this.estimateTokens(prompt);
|
|
56
|
+
const completionTokens = this.estimateTokens(summary);
|
|
57
|
+
return {
|
|
58
|
+
id: this.generateId(),
|
|
59
|
+
model: options.model,
|
|
60
|
+
summary,
|
|
61
|
+
usage: {
|
|
62
|
+
promptTokens,
|
|
63
|
+
completionTokens,
|
|
64
|
+
totalTokens: promptTokens + completionTokens
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async createEmbeddings(options) {
|
|
69
|
+
const inputs = Array.isArray(options.input) ? options.input : [options.input];
|
|
70
|
+
const result = await this.client.models.embedContent({
|
|
71
|
+
model: options.model,
|
|
72
|
+
contents: inputs
|
|
73
|
+
});
|
|
74
|
+
const embeddings = [];
|
|
75
|
+
if (result.embeddings && Array.isArray(result.embeddings)) {
|
|
76
|
+
for (const embedding of result.embeddings) {
|
|
77
|
+
if (embedding.values && Array.isArray(embedding.values)) {
|
|
78
|
+
embeddings.push(embedding.values);
|
|
79
|
+
} else if (Array.isArray(embedding)) {
|
|
80
|
+
embeddings.push(embedding);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const promptTokens = inputs.reduce(
|
|
85
|
+
(sum, input) => sum + this.estimateTokens(input),
|
|
86
|
+
0
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
id: this.generateId(),
|
|
90
|
+
model: options.model || "gemini-embedding-001",
|
|
91
|
+
embeddings,
|
|
92
|
+
usage: {
|
|
93
|
+
promptTokens,
|
|
94
|
+
totalTokens: promptTokens
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
buildSummarizationPrompt(options, text) {
|
|
99
|
+
let prompt = "You are a professional summarizer. ";
|
|
100
|
+
switch (options.style) {
|
|
101
|
+
case "bullet-points":
|
|
102
|
+
prompt += "Provide a summary in bullet point format. ";
|
|
103
|
+
break;
|
|
104
|
+
case "paragraph":
|
|
105
|
+
prompt += "Provide a summary in paragraph format. ";
|
|
106
|
+
break;
|
|
107
|
+
case "concise":
|
|
108
|
+
prompt += "Provide a very concise summary in 1-2 sentences. ";
|
|
109
|
+
break;
|
|
110
|
+
default:
|
|
111
|
+
prompt += "Provide a clear and concise summary. ";
|
|
112
|
+
}
|
|
113
|
+
if (options.focus && options.focus.length > 0) {
|
|
114
|
+
prompt += `Focus on the following aspects: ${options.focus.join(", ")}. `;
|
|
115
|
+
}
|
|
116
|
+
prompt += `
|
|
117
|
+
|
|
118
|
+
Text to summarize:
|
|
119
|
+
${text}
|
|
120
|
+
|
|
121
|
+
Summary:`;
|
|
122
|
+
return prompt;
|
|
123
|
+
}
|
|
124
|
+
estimateTokens(text) {
|
|
125
|
+
return Math.ceil(text.length / 4);
|
|
126
|
+
}
|
|
127
|
+
async *processStreamChunks(result, model) {
|
|
128
|
+
const timestamp = Date.now();
|
|
129
|
+
let accumulatedContent = "";
|
|
130
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
131
|
+
let nextToolIndex = 0;
|
|
132
|
+
for await (const chunk of result) {
|
|
133
|
+
if (chunk.candidates?.[0]?.content?.parts) {
|
|
134
|
+
const parts = chunk.candidates[0].content.parts;
|
|
135
|
+
for (const part of parts) {
|
|
136
|
+
if (part.text) {
|
|
137
|
+
accumulatedContent += part.text;
|
|
138
|
+
yield {
|
|
139
|
+
type: "content",
|
|
140
|
+
id: this.generateId(),
|
|
141
|
+
model,
|
|
142
|
+
timestamp,
|
|
143
|
+
delta: part.text,
|
|
144
|
+
content: accumulatedContent,
|
|
145
|
+
role: "assistant"
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const functionCall = part.functionCall;
|
|
149
|
+
if (functionCall) {
|
|
150
|
+
const toolCallId = functionCall.name || `call_${Date.now()}_${nextToolIndex}`;
|
|
151
|
+
const functionArgs = functionCall.args || {};
|
|
152
|
+
let toolCallData = toolCallMap.get(toolCallId);
|
|
153
|
+
if (!toolCallData) {
|
|
154
|
+
toolCallData = {
|
|
155
|
+
name: functionCall.name || "",
|
|
156
|
+
args: typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs),
|
|
157
|
+
index: nextToolIndex++
|
|
158
|
+
};
|
|
159
|
+
toolCallMap.set(toolCallId, toolCallData);
|
|
160
|
+
} else {
|
|
161
|
+
try {
|
|
162
|
+
const existingArgs = JSON.parse(toolCallData.args);
|
|
163
|
+
const newArgs = typeof functionArgs === "string" ? JSON.parse(functionArgs) : functionArgs;
|
|
164
|
+
const mergedArgs = { ...existingArgs, ...newArgs };
|
|
165
|
+
toolCallData.args = JSON.stringify(mergedArgs);
|
|
166
|
+
} catch {
|
|
167
|
+
toolCallData.args = typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
yield {
|
|
171
|
+
type: "tool_call",
|
|
172
|
+
id: this.generateId(),
|
|
173
|
+
model,
|
|
174
|
+
timestamp,
|
|
175
|
+
toolCall: {
|
|
176
|
+
id: toolCallId,
|
|
177
|
+
type: "function",
|
|
178
|
+
function: {
|
|
179
|
+
name: toolCallData.name,
|
|
180
|
+
arguments: toolCallData.args
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
index: toolCallData.index
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else if (chunk.data) {
|
|
188
|
+
accumulatedContent += chunk.data;
|
|
189
|
+
yield {
|
|
190
|
+
type: "content",
|
|
191
|
+
id: this.generateId(),
|
|
192
|
+
model,
|
|
193
|
+
timestamp,
|
|
194
|
+
delta: chunk.data,
|
|
195
|
+
content: accumulatedContent,
|
|
196
|
+
role: "assistant"
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (chunk.candidates?.[0]?.finishReason) {
|
|
200
|
+
const finishReason = chunk.candidates[0].finishReason;
|
|
201
|
+
if (finishReason === FinishReason.UNEXPECTED_TOOL_CALL) {
|
|
202
|
+
if (chunk.candidates[0].content?.parts) {
|
|
203
|
+
for (const part of chunk.candidates[0].content.parts) {
|
|
204
|
+
const functionCall = part.functionCall;
|
|
205
|
+
if (functionCall) {
|
|
206
|
+
const toolCallId = functionCall.name || `call_${Date.now()}_${nextToolIndex}`;
|
|
207
|
+
const functionArgs = functionCall.args || {};
|
|
208
|
+
toolCallMap.set(toolCallId, {
|
|
209
|
+
name: functionCall.name || "",
|
|
210
|
+
args: typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs),
|
|
211
|
+
index: nextToolIndex++
|
|
212
|
+
});
|
|
213
|
+
yield {
|
|
214
|
+
type: "tool_call",
|
|
215
|
+
id: this.generateId(),
|
|
216
|
+
model,
|
|
217
|
+
timestamp,
|
|
218
|
+
toolCall: {
|
|
219
|
+
id: toolCallId,
|
|
220
|
+
type: "function",
|
|
221
|
+
function: {
|
|
222
|
+
name: functionCall.name || "",
|
|
223
|
+
arguments: typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs)
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
index: nextToolIndex - 1
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (finishReason === FinishReason.MAX_TOKENS) {
|
|
233
|
+
yield {
|
|
234
|
+
type: "error",
|
|
235
|
+
id: this.generateId(),
|
|
236
|
+
model,
|
|
237
|
+
timestamp,
|
|
238
|
+
error: {
|
|
239
|
+
message: "The response was cut off because the maximum token limit was reached."
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
yield {
|
|
244
|
+
type: "done",
|
|
245
|
+
id: this.generateId(),
|
|
246
|
+
model,
|
|
247
|
+
timestamp,
|
|
248
|
+
finishReason: toolCallMap.size > 0 ? "tool_calls" : "stop",
|
|
249
|
+
usage: chunk.usageMetadata ? {
|
|
250
|
+
promptTokens: chunk.usageMetadata.promptTokenCount ?? 0,
|
|
251
|
+
completionTokens: chunk.usageMetadata.thoughtsTokenCount ?? 0,
|
|
252
|
+
totalTokens: chunk.usageMetadata.totalTokenCount ?? 0
|
|
253
|
+
} : void 0
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
convertContentPartToGemini(part) {
|
|
259
|
+
switch (part.type) {
|
|
260
|
+
case "text":
|
|
261
|
+
return { text: part.content };
|
|
262
|
+
case "image":
|
|
263
|
+
case "audio":
|
|
264
|
+
case "video":
|
|
265
|
+
case "document": {
|
|
266
|
+
const metadata = part.metadata;
|
|
267
|
+
if (part.source.type === "data") {
|
|
268
|
+
return {
|
|
269
|
+
inlineData: {
|
|
270
|
+
data: part.source.value,
|
|
271
|
+
mimeType: metadata?.mimeType ?? "image/jpeg"
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
} else {
|
|
275
|
+
return {
|
|
276
|
+
fileData: {
|
|
277
|
+
fileUri: part.source.value,
|
|
278
|
+
mimeType: metadata?.mimeType ?? "image/jpeg"
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
default: {
|
|
284
|
+
const _exhaustiveCheck = part;
|
|
285
|
+
throw new Error(
|
|
286
|
+
`Unsupported content part type: ${_exhaustiveCheck.type}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
formatMessages(messages) {
|
|
292
|
+
return messages.map((msg) => {
|
|
293
|
+
const role = msg.role === "assistant" ? "model" : "user";
|
|
294
|
+
const parts = [];
|
|
295
|
+
if (Array.isArray(msg.content)) {
|
|
296
|
+
for (const contentPart of msg.content) {
|
|
297
|
+
parts.push(this.convertContentPartToGemini(contentPart));
|
|
298
|
+
}
|
|
299
|
+
} else if (msg.content) {
|
|
300
|
+
parts.push({ text: msg.content });
|
|
301
|
+
}
|
|
302
|
+
if (msg.role === "assistant" && msg.toolCalls?.length) {
|
|
303
|
+
for (const toolCall of msg.toolCalls) {
|
|
304
|
+
let parsedArgs = {};
|
|
305
|
+
try {
|
|
306
|
+
parsedArgs = toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : {};
|
|
307
|
+
} catch {
|
|
308
|
+
parsedArgs = toolCall.function.arguments;
|
|
309
|
+
}
|
|
310
|
+
parts.push({
|
|
311
|
+
functionCall: {
|
|
312
|
+
name: toolCall.function.name,
|
|
313
|
+
args: parsedArgs
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (msg.role === "tool" && msg.toolCallId) {
|
|
319
|
+
parts.push({
|
|
320
|
+
functionResponse: {
|
|
321
|
+
name: msg.toolCallId,
|
|
322
|
+
// Gemini uses function name here
|
|
323
|
+
response: {
|
|
324
|
+
content: msg.content || ""
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
role,
|
|
331
|
+
parts: parts.length > 0 ? parts : [{ text: "" }]
|
|
332
|
+
};
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Maps common options to Gemini-specific format
|
|
337
|
+
* Handles translation of normalized options to Gemini's API format
|
|
338
|
+
*/
|
|
339
|
+
mapCommonOptionsToGemini(options) {
|
|
340
|
+
const providerOpts = options.providerOptions;
|
|
341
|
+
const requestOptions = {
|
|
342
|
+
model: options.model,
|
|
343
|
+
contents: this.formatMessages(options.messages),
|
|
344
|
+
config: {
|
|
345
|
+
...providerOpts,
|
|
346
|
+
temperature: options.options?.temperature,
|
|
347
|
+
topP: options.options?.topP,
|
|
348
|
+
maxOutputTokens: options.options?.maxTokens,
|
|
349
|
+
systemInstruction: options.systemPrompts?.join("\n"),
|
|
350
|
+
...providerOpts?.generationConfig,
|
|
351
|
+
tools: convertToolsToProviderFormat(options.tools)
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
return requestOptions;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function createGemini(apiKey, config) {
|
|
358
|
+
return new GeminiAdapter({ apiKey, ...config });
|
|
359
|
+
}
|
|
360
|
+
function gemini(config) {
|
|
361
|
+
const env = typeof globalThis !== "undefined" && globalThis.window?.env ? globalThis.window.env : typeof process !== "undefined" ? process.env : void 0;
|
|
362
|
+
const key = env?.GOOGLE_API_KEY || env?.GEMINI_API_KEY;
|
|
363
|
+
if (!key) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
"GOOGLE_API_KEY or GEMINI_API_KEY is required. Please set it in your environment variables or use createGemini(apiKey, config) instead."
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return createGemini(key, config);
|
|
369
|
+
}
|
|
370
|
+
export {
|
|
371
|
+
GeminiAdapter,
|
|
372
|
+
createGemini,
|
|
373
|
+
gemini
|
|
374
|
+
};
|
|
375
|
+
//# sourceMappingURL=gemini-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini-adapter.js","sources":["../../src/gemini-adapter.ts"],"sourcesContent":["import { FinishReason, GoogleGenAI } from '@google/genai'\nimport { BaseAdapter } from '@tanstack/ai'\nimport { GEMINI_EMBEDDING_MODELS, GEMINI_MODELS } from './model-meta'\nimport { convertToolsToProviderFormat } from './tools/tool-converter'\nimport type {\n AIAdapterConfig,\n ChatOptions,\n ContentPart,\n EmbeddingOptions,\n EmbeddingResult,\n ModelMessage,\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n} from '@tanstack/ai'\nimport type {\n GeminiChatModelProviderOptionsByName,\n GeminiModelInputModalitiesByName,\n} from './model-meta'\nimport type { ExternalTextProviderOptions } from './text/text-provider-options'\nimport type {\n GenerateContentParameters,\n GenerateContentResponse,\n Part,\n} from '@google/genai'\nimport type {\n GeminiAudioMetadata,\n GeminiDocumentMetadata,\n GeminiImageMetadata,\n GeminiMessageMetadataByModality,\n GeminiVideoMetadata,\n} from './message-types'\n\nexport interface GeminiAdapterConfig extends AIAdapterConfig {\n apiKey: string\n}\n\n/**\n * Gemini-specific provider options\n * Based on Google Generative AI SDK\n * @see https://ai.google.dev/api/rest/v1/GenerationConfig\n */\nexport type GeminiProviderOptions = ExternalTextProviderOptions\n\nexport class GeminiAdapter extends BaseAdapter<\n typeof GEMINI_MODELS,\n typeof GEMINI_EMBEDDING_MODELS,\n GeminiProviderOptions,\n Record<string, any>,\n GeminiChatModelProviderOptionsByName,\n GeminiModelInputModalitiesByName,\n GeminiMessageMetadataByModality\n> {\n name = 'gemini'\n models = GEMINI_MODELS\n embeddingModels = GEMINI_EMBEDDING_MODELS\n declare _modelProviderOptionsByName: GeminiChatModelProviderOptionsByName\n declare _modelInputModalitiesByName: GeminiModelInputModalitiesByName\n declare _messageMetadataByModality: GeminiMessageMetadataByModality\n private client: GoogleGenAI\n\n constructor(config: GeminiAdapterConfig) {\n super(config)\n this.client = new GoogleGenAI({\n apiKey: config.apiKey,\n })\n }\n\n async *chatStream(\n options: ChatOptions<string, GeminiProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n // Map common options to Gemini format\n const mappedOptions = this.mapCommonOptionsToGemini(options)\n\n try {\n const result =\n await this.client.models.generateContentStream(mappedOptions)\n\n yield* this.processStreamChunks(result, options.model)\n } catch (error) {\n const timestamp = Date.now()\n yield {\n type: 'error',\n id: this.generateId(),\n model: options.model,\n timestamp,\n error: {\n message:\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during the chat stream.',\n },\n }\n }\n }\n\n async summarize(options: SummarizationOptions): Promise<SummarizationResult> {\n const prompt = this.buildSummarizationPrompt(options, options.text)\n\n // Use models API like chatCompletion\n const result = await this.client.models.generateContent({\n model: options.model,\n contents: [{ role: 'user', parts: [{ text: prompt }] }],\n config: {\n temperature: 0.3,\n maxOutputTokens: options.maxLength || 500,\n },\n })\n\n // Extract text from candidates or use .text() method\n let summary = ''\n if (result.candidates?.[0]?.content?.parts) {\n const parts = result.candidates[0].content.parts\n for (const part of parts) {\n if (part.text) {\n summary += part.text\n }\n }\n }\n\n if (!summary && typeof result.text === 'string') {\n summary = result.text\n }\n\n const promptTokens = this.estimateTokens(prompt)\n const completionTokens = this.estimateTokens(summary)\n\n return {\n id: this.generateId(),\n model: options.model,\n summary,\n usage: {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n },\n }\n }\n\n async createEmbeddings(options: EmbeddingOptions): Promise<EmbeddingResult> {\n const inputs = Array.isArray(options.input)\n ? options.input\n : [options.input]\n\n // According to docs: contents can be a string or array of strings\n // Response has embeddings (plural) array with values property\n const result = await this.client.models.embedContent({\n model: options.model,\n contents: inputs,\n })\n\n // Extract embeddings from result.embeddings array\n const embeddings: Array<Array<number>> = []\n if (result.embeddings && Array.isArray(result.embeddings)) {\n for (const embedding of result.embeddings) {\n if (embedding.values && Array.isArray(embedding.values)) {\n embeddings.push(embedding.values)\n } else if (Array.isArray(embedding)) {\n embeddings.push(embedding)\n }\n }\n }\n\n const promptTokens = inputs.reduce(\n (sum, input) => sum + this.estimateTokens(input),\n 0,\n )\n\n return {\n id: this.generateId(),\n model: options.model || 'gemini-embedding-001',\n embeddings,\n usage: {\n promptTokens,\n totalTokens: promptTokens,\n },\n }\n }\n\n private buildSummarizationPrompt(\n options: SummarizationOptions,\n text: string,\n ): string {\n let prompt = 'You are a professional summarizer. '\n\n switch (options.style) {\n case 'bullet-points':\n prompt += 'Provide a summary in bullet point format. '\n break\n case 'paragraph':\n prompt += 'Provide a summary in paragraph format. '\n break\n case 'concise':\n prompt += 'Provide a very concise summary in 1-2 sentences. '\n break\n default:\n prompt += 'Provide a clear and concise summary. '\n }\n\n if (options.focus && options.focus.length > 0) {\n prompt += `Focus on the following aspects: ${options.focus.join(', ')}. `\n }\n\n prompt += `\\n\\nText to summarize:\\n${text}\\n\\nSummary:`\n\n return prompt\n }\n\n private estimateTokens(text: string): number {\n // Rough approximation: 1 token ≈ 4 characters\n return Math.ceil(text.length / 4)\n }\n\n private async *processStreamChunks(\n result: AsyncGenerator<GenerateContentResponse, any, any>,\n model: string,\n ): AsyncIterable<StreamChunk> {\n const timestamp = Date.now()\n let accumulatedContent = ''\n const toolCallMap = new Map<\n string,\n { name: string; args: string; index: number }\n >()\n let nextToolIndex = 0\n // Iterate over the stream result (it's already an AsyncGenerator)\n for await (const chunk of result) {\n // Extract content from candidates[0].content.parts\n // Parts can contain text or functionCall\n if (chunk.candidates?.[0]?.content?.parts) {\n const parts = chunk.candidates[0].content.parts\n\n for (const part of parts) {\n // Handle text content\n if (part.text) {\n accumulatedContent += part.text\n yield {\n type: 'content',\n id: this.generateId(),\n model,\n timestamp,\n delta: part.text,\n content: accumulatedContent,\n role: 'assistant',\n }\n }\n\n // Handle function calls (tool calls)\n // Check both camelCase (SDK) and snake_case (direct API) formats\n const functionCall = part.functionCall\n if (functionCall) {\n const toolCallId =\n functionCall.name || `call_${Date.now()}_${nextToolIndex}`\n const functionArgs = functionCall.args || {}\n\n // Check if we've seen this tool call before (for streaming args)\n let toolCallData = toolCallMap.get(toolCallId)\n if (!toolCallData) {\n toolCallData = {\n name: functionCall.name || '',\n args:\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs),\n index: nextToolIndex++,\n }\n toolCallMap.set(toolCallId, toolCallData)\n } else {\n // Merge arguments if streaming\n\n try {\n const existingArgs = JSON.parse(toolCallData.args)\n const newArgs =\n typeof functionArgs === 'string'\n ? JSON.parse(functionArgs)\n : functionArgs\n const mergedArgs = { ...existingArgs, ...newArgs }\n toolCallData.args = JSON.stringify(mergedArgs)\n } catch {\n // If parsing fails, use new args\n toolCallData.args =\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs)\n }\n }\n\n yield {\n type: 'tool_call',\n id: this.generateId(),\n model,\n timestamp,\n toolCall: {\n id: toolCallId,\n type: 'function',\n function: {\n name: toolCallData.name,\n arguments: toolCallData.args,\n },\n },\n index: toolCallData.index,\n }\n }\n }\n } else if (chunk.data) {\n // Fallback to chunk.data if available\n accumulatedContent += chunk.data\n yield {\n type: 'content',\n id: this.generateId(),\n model,\n timestamp,\n delta: chunk.data,\n content: accumulatedContent,\n role: 'assistant',\n }\n }\n\n // Check for finish reason\n if (chunk.candidates?.[0]?.finishReason) {\n const finishReason = chunk.candidates[0].finishReason\n\n // UNEXPECTED_TOOL_CALL means Gemini tried to call a function but it wasn't properly declared\n // This typically means there's an issue with the tool declaration format\n // We should map it to tool_calls to try to process it anyway\n if (finishReason === FinishReason.UNEXPECTED_TOOL_CALL) {\n // Try to extract function call from content.parts if available\n if (chunk.candidates[0].content?.parts) {\n for (const part of chunk.candidates[0].content.parts) {\n const functionCall = part.functionCall\n if (functionCall) {\n // We found a function call - process it\n const toolCallId =\n functionCall.name || `call_${Date.now()}_${nextToolIndex}`\n const functionArgs = functionCall.args || {}\n\n toolCallMap.set(toolCallId, {\n name: functionCall.name || '',\n args:\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs),\n index: nextToolIndex++,\n })\n\n yield {\n type: 'tool_call',\n id: this.generateId(),\n model,\n timestamp,\n toolCall: {\n id: toolCallId,\n type: 'function',\n function: {\n name: functionCall.name || '',\n arguments:\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs),\n },\n },\n index: nextToolIndex - 1,\n }\n }\n }\n }\n }\n if (finishReason === FinishReason.MAX_TOKENS) {\n yield {\n type: 'error',\n id: this.generateId(),\n model,\n timestamp,\n error: {\n message:\n 'The response was cut off because the maximum token limit was reached.',\n },\n }\n }\n\n yield {\n type: 'done',\n id: this.generateId(),\n model,\n timestamp,\n finishReason: toolCallMap.size > 0 ? 'tool_calls' : 'stop',\n usage: chunk.usageMetadata\n ? {\n promptTokens: chunk.usageMetadata.promptTokenCount ?? 0,\n completionTokens: chunk.usageMetadata.thoughtsTokenCount ?? 0,\n totalTokens: chunk.usageMetadata.totalTokenCount ?? 0,\n }\n : undefined,\n }\n }\n }\n }\n\n private convertContentPartToGemini(part: ContentPart): Part {\n switch (part.type) {\n case 'text':\n return { text: part.content }\n case 'image':\n case 'audio':\n case 'video':\n case 'document': {\n const metadata = part.metadata as\n | GeminiDocumentMetadata\n | GeminiImageMetadata\n | GeminiVideoMetadata\n | GeminiAudioMetadata\n | undefined\n // Gemini uses inlineData for base64 and fileData for URLs\n if (part.source.type === 'data') {\n return {\n inlineData: {\n data: part.source.value,\n mimeType: metadata?.mimeType ?? 'image/jpeg',\n },\n }\n } else {\n return {\n fileData: {\n fileUri: part.source.value,\n mimeType: metadata?.mimeType ?? 'image/jpeg',\n },\n }\n }\n }\n default: {\n // Exhaustive check - this should never happen with known types\n const _exhaustiveCheck: never = part\n throw new Error(\n `Unsupported content part type: ${(_exhaustiveCheck as ContentPart).type}`,\n )\n }\n }\n }\n\n private formatMessages(\n messages: Array<ModelMessage>,\n ): GenerateContentParameters['contents'] {\n return messages.map((msg) => {\n const role: 'user' | 'model' = msg.role === 'assistant' ? 'model' : 'user'\n const parts: Array<Part> = []\n\n // Handle multimodal content (array of ContentPart)\n if (Array.isArray(msg.content)) {\n for (const contentPart of msg.content) {\n parts.push(this.convertContentPartToGemini(contentPart))\n }\n } else if (msg.content) {\n // Handle string content (backward compatibility)\n parts.push({ text: msg.content })\n }\n\n // Handle tool calls (from assistant)\n if (msg.role === 'assistant' && msg.toolCalls?.length) {\n for (const toolCall of msg.toolCalls) {\n let parsedArgs: Record<string, unknown> = {}\n try {\n parsedArgs = toolCall.function.arguments\n ? JSON.parse(toolCall.function.arguments)\n : {}\n } catch {\n parsedArgs = toolCall.function.arguments as any\n }\n\n parts.push({\n functionCall: {\n name: toolCall.function.name,\n args: parsedArgs,\n },\n })\n }\n }\n\n // Handle tool results (from tool role)\n if (msg.role === 'tool' && msg.toolCallId) {\n parts.push({\n functionResponse: {\n name: msg.toolCallId, // Gemini uses function name here\n response: {\n content: msg.content || '',\n },\n },\n })\n }\n\n return {\n role,\n parts: parts.length > 0 ? parts : [{ text: '' }],\n }\n })\n }\n /**\n * Maps common options to Gemini-specific format\n * Handles translation of normalized options to Gemini's API format\n */\n private mapCommonOptionsToGemini(options: ChatOptions) {\n const providerOpts = options.providerOptions\n const requestOptions: GenerateContentParameters = {\n model: options.model,\n contents: this.formatMessages(options.messages),\n config: {\n ...providerOpts,\n temperature: options.options?.temperature,\n topP: options.options?.topP,\n maxOutputTokens: options.options?.maxTokens,\n systemInstruction: options.systemPrompts?.join('\\n'),\n ...providerOpts?.generationConfig,\n tools: convertToolsToProviderFormat(options.tools),\n },\n }\n\n return requestOptions\n }\n}\n\n/**\n * Creates a Gemini adapter with simplified configuration\n * @param apiKey - Your Google API key\n * @returns A fully configured Gemini adapter instance\n *\n * @example\n * ```typescript\n * const gemini = createGemini(\"AIza...\");\n *\n * const ai = new AI({\n * adapters: {\n * gemini,\n * }\n * });\n * ```\n */\nexport function createGemini(\n apiKey: string,\n config?: Omit<GeminiAdapterConfig, 'apiKey'>,\n): GeminiAdapter {\n return new GeminiAdapter({ apiKey, ...config })\n}\n\n/**\n * Create a Gemini adapter with automatic API key detection from environment variables.\n *\n * Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Gemini adapter instance\n * @throws Error if API key is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses GOOGLE_API_KEY or GEMINI_API_KEY from environment\n * const aiInstance = ai(gemini());\n * ```\n */\nexport function gemini(\n config?: Omit<GeminiAdapterConfig, 'apiKey'>,\n): GeminiAdapter {\n const env =\n typeof globalThis !== 'undefined' && (globalThis as any).window?.env\n ? (globalThis as any).window.env\n : typeof process !== 'undefined'\n ? process.env\n : undefined\n const key = env?.GOOGLE_API_KEY || env?.GEMINI_API_KEY\n\n if (!key) {\n throw new Error(\n 'GOOGLE_API_KEY or GEMINI_API_KEY is required. Please set it in your environment variables or use createGemini(apiKey, config) instead.',\n )\n }\n\n return createGemini(key, config)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { GeminiAdapter, createGemini, gemini } from './gemini-adapter.js';
|
|
2
|
+
export type { GeminiAdapterConfig } from './gemini-adapter.js';
|
|
3
|
+
export type { GeminiChatModelProviderOptionsByName, GeminiModelInputModalitiesByName, } from './model-meta.js';
|
|
4
|
+
export type { GeminiStructuredOutputOptions, GeminiThinkingOptions, } from './text/text-provider-options.js';
|
|
5
|
+
export type { GoogleGeminiTool } from './tools/index.js';
|
|
6
|
+
export type { GeminiImageMetadata, GeminiAudioMetadata, GeminiVideoMetadata, GeminiDocumentMetadata, GeminiImageMimeType, GeminiAudioMimeType, GeminiVideoMimeType, GeminiDocumentMimeType, GeminiMessageMetadataByModality, } from './message-types.js';
|