@tanstack/ai-gemini 0.10.3 → 0.10.5
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/dist/esm/adapters/summarize.d.ts +23 -48
- package/dist/esm/adapters/summarize.js +9 -159
- package/dist/esm/adapters/summarize.js.map +1 -1
- package/dist/esm/adapters/text.js +94 -95
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -3
- package/package.json +3 -3
- package/src/adapters/summarize.ts +37 -252
- package/src/adapters/text.ts +94 -99
- package/src/index.ts +2 -5
|
@@ -1,58 +1,33 @@
|
|
|
1
|
+
import { ChatStreamSummarizeAdapter, InferTextProviderOptions } from '@tanstack/ai/adapters';
|
|
2
|
+
import { GeminiTextAdapter } from './text.js';
|
|
3
|
+
import { GEMINI_MODELS } from '../model-meta.js';
|
|
1
4
|
import { GeminiClientConfig } from '../utils.js';
|
|
2
|
-
import { SummarizeAdapter } from '@tanstack/ai/adapters';
|
|
3
|
-
import { StreamChunk, SummarizationOptions, SummarizationResult } from '@tanstack/ai';
|
|
4
5
|
/**
|
|
5
6
|
* Configuration for Gemini summarize adapter
|
|
6
7
|
*/
|
|
7
8
|
export interface GeminiSummarizeConfig extends GeminiClientConfig {
|
|
8
9
|
}
|
|
10
|
+
export type GeminiSummarizeModel = (typeof GEMINI_MODELS)[number];
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
12
|
+
* Creates a Gemini summarize adapter with explicit API key and model.
|
|
13
|
+
*
|
|
14
|
+
* Note: keeps the historical (apiKey, model, config) argument order to
|
|
15
|
+
* avoid breaking existing callers.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const adapter = createGeminiSummarize('AIza...', 'gemini-2.0-flash');
|
|
20
|
+
* ```
|
|
11
21
|
*/
|
|
12
|
-
export declare
|
|
13
|
-
export type GeminiSummarizeModel = (typeof GeminiSummarizeModels)[number];
|
|
22
|
+
export declare function createGeminiSummarize<TModel extends GeminiSummarizeModel>(apiKey: string, model: TModel, config?: Omit<GeminiSummarizeConfig, 'apiKey'>): ChatStreamSummarizeAdapter<TModel, InferTextProviderOptions<GeminiTextAdapter<TModel>>>;
|
|
14
23
|
/**
|
|
15
|
-
*
|
|
24
|
+
* Creates a Gemini summarize adapter with API key from `GOOGLE_API_KEY` /
|
|
25
|
+
* `GEMINI_API_KEY` environment variables.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const adapter = geminiSummarize('gemini-2.0-flash');
|
|
30
|
+
* await summarize({ adapter, text: 'Long article text...' });
|
|
31
|
+
* ```
|
|
16
32
|
*/
|
|
17
|
-
export
|
|
18
|
-
/** Generation configuration */
|
|
19
|
-
generationConfig?: {
|
|
20
|
-
temperature?: number;
|
|
21
|
-
topP?: number;
|
|
22
|
-
topK?: number;
|
|
23
|
-
maxOutputTokens?: number;
|
|
24
|
-
stopSequences?: Array<string>;
|
|
25
|
-
};
|
|
26
|
-
/** Safety settings */
|
|
27
|
-
safetySettings?: Array<{
|
|
28
|
-
category: string;
|
|
29
|
-
threshold: string;
|
|
30
|
-
}>;
|
|
31
|
-
}
|
|
32
|
-
export interface GeminiSummarizeAdapterOptions {
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Gemini Summarize Adapter
|
|
36
|
-
* A tree-shakeable summarization adapter for Google Gemini
|
|
37
|
-
*/
|
|
38
|
-
export declare class GeminiSummarizeAdapter<TModel extends GeminiSummarizeModel> implements SummarizeAdapter<TModel, GeminiSummarizeProviderOptions> {
|
|
39
|
-
readonly kind: "summarize";
|
|
40
|
-
readonly name: "gemini";
|
|
41
|
-
readonly model: TModel;
|
|
42
|
-
'~types': {
|
|
43
|
-
providerOptions: GeminiSummarizeProviderOptions;
|
|
44
|
-
};
|
|
45
|
-
private client;
|
|
46
|
-
constructor(config: GeminiSummarizeConfig, model: TModel);
|
|
47
|
-
summarize(options: SummarizationOptions): Promise<SummarizationResult>;
|
|
48
|
-
summarizeStream(options: SummarizationOptions): AsyncIterable<StreamChunk>;
|
|
49
|
-
private getFormatInstructions;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Creates a Gemini summarize adapter with explicit API key and model
|
|
53
|
-
*/
|
|
54
|
-
export declare function createGeminiSummarize<TModel extends GeminiSummarizeModel>(apiKey: string, model: TModel, config?: Omit<GeminiSummarizeConfig, 'apiKey'>): GeminiSummarizeAdapter<TModel>;
|
|
55
|
-
/**
|
|
56
|
-
* Creates a Gemini summarize adapter with API key from environment and required model
|
|
57
|
-
*/
|
|
58
|
-
export declare function geminiSummarize<TModel extends GeminiSummarizeModel>(model: TModel, config?: Omit<GeminiSummarizeConfig, 'apiKey'>): GeminiSummarizeAdapter<TModel>;
|
|
33
|
+
export declare function geminiSummarize<TModel extends GeminiSummarizeModel>(model: TModel, config?: Omit<GeminiSummarizeConfig, 'apiKey'>): ChatStreamSummarizeAdapter<TModel, InferTextProviderOptions<GeminiTextAdapter<TModel>>>;
|
|
@@ -1,167 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const GeminiSummarizeModels = [
|
|
5
|
-
"gemini-3.1-flash-lite-preview",
|
|
6
|
-
"gemini-2.0-flash",
|
|
7
|
-
"gemini-1.5-flash",
|
|
8
|
-
"gemini-1.5-pro",
|
|
9
|
-
"gemini-2.0-flash-lite"
|
|
10
|
-
];
|
|
11
|
-
class GeminiSummarizeAdapter {
|
|
12
|
-
constructor(config, model) {
|
|
13
|
-
this.kind = "summarize";
|
|
14
|
-
this.name = "gemini";
|
|
15
|
-
this.client = createGeminiClient(config);
|
|
16
|
-
this.model = model;
|
|
17
|
-
}
|
|
18
|
-
async summarize(options) {
|
|
19
|
-
const { logger } = options;
|
|
20
|
-
const model = options.model;
|
|
21
|
-
logger.request(`activity=summarize provider=gemini`, {
|
|
22
|
-
provider: "gemini",
|
|
23
|
-
model
|
|
24
|
-
});
|
|
25
|
-
const formatInstructions = this.getFormatInstructions(options.style);
|
|
26
|
-
const lengthInstructions = options.maxLength ? ` Keep the summary under ${options.maxLength} tokens.` : "";
|
|
27
|
-
const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`;
|
|
28
|
-
try {
|
|
29
|
-
const response = await this.client.models.generateContent({
|
|
30
|
-
model,
|
|
31
|
-
contents: [
|
|
32
|
-
{
|
|
33
|
-
role: "user",
|
|
34
|
-
parts: [
|
|
35
|
-
{ text: `Please summarize the following:
|
|
36
|
-
|
|
37
|
-
${options.text}` }
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
config: {
|
|
42
|
-
systemInstruction: systemPrompt
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
const summary = response.text ?? "";
|
|
46
|
-
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
47
|
-
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
48
|
-
return {
|
|
49
|
-
id: generateId("sum"),
|
|
50
|
-
model,
|
|
51
|
-
summary,
|
|
52
|
-
usage: {
|
|
53
|
-
promptTokens: inputTokens,
|
|
54
|
-
completionTokens: outputTokens,
|
|
55
|
-
totalTokens: inputTokens + outputTokens
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
} catch (error) {
|
|
59
|
-
logger.errors("gemini.summarize fatal", {
|
|
60
|
-
error,
|
|
61
|
-
source: "gemini.summarize"
|
|
62
|
-
});
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async *summarizeStream(options) {
|
|
67
|
-
const { logger } = options;
|
|
68
|
-
const model = options.model;
|
|
69
|
-
const id = generateId("sum");
|
|
70
|
-
let accumulatedContent = "";
|
|
71
|
-
let inputTokens = 0;
|
|
72
|
-
let outputTokens = 0;
|
|
73
|
-
const formatInstructions = this.getFormatInstructions(options.style);
|
|
74
|
-
const lengthInstructions = options.maxLength ? ` Keep the summary under ${options.maxLength} words.` : "";
|
|
75
|
-
const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`;
|
|
76
|
-
logger.request(`activity=summarize provider=gemini`, {
|
|
77
|
-
provider: "gemini",
|
|
78
|
-
model,
|
|
79
|
-
stream: true
|
|
80
|
-
});
|
|
81
|
-
try {
|
|
82
|
-
const result = await this.client.models.generateContentStream({
|
|
83
|
-
model,
|
|
84
|
-
contents: [
|
|
85
|
-
{
|
|
86
|
-
role: "user",
|
|
87
|
-
parts: [
|
|
88
|
-
{ text: `Please summarize the following:
|
|
89
|
-
|
|
90
|
-
${options.text}` }
|
|
91
|
-
]
|
|
92
|
-
}
|
|
93
|
-
],
|
|
94
|
-
config: {
|
|
95
|
-
systemInstruction: systemPrompt
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
for await (const chunk of result) {
|
|
99
|
-
logger.provider(`provider=gemini`, { chunk });
|
|
100
|
-
if (chunk.usageMetadata) {
|
|
101
|
-
inputTokens = chunk.usageMetadata.promptTokenCount ?? inputTokens;
|
|
102
|
-
outputTokens = chunk.usageMetadata.candidatesTokenCount ?? outputTokens;
|
|
103
|
-
}
|
|
104
|
-
if (chunk.candidates?.[0]?.content?.parts) {
|
|
105
|
-
for (const part of chunk.candidates[0].content.parts) {
|
|
106
|
-
if (part.text) {
|
|
107
|
-
accumulatedContent += part.text;
|
|
108
|
-
yield asChunk({
|
|
109
|
-
type: "TEXT_MESSAGE_CONTENT",
|
|
110
|
-
messageId: id,
|
|
111
|
-
model,
|
|
112
|
-
timestamp: Date.now(),
|
|
113
|
-
delta: part.text,
|
|
114
|
-
content: accumulatedContent
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
const finishReason = chunk.candidates?.[0]?.finishReason;
|
|
120
|
-
if (finishReason === FinishReason.STOP || finishReason === FinishReason.MAX_TOKENS || finishReason === FinishReason.SAFETY) {
|
|
121
|
-
yield asChunk({
|
|
122
|
-
type: "RUN_FINISHED",
|
|
123
|
-
runId: id,
|
|
124
|
-
model,
|
|
125
|
-
timestamp: Date.now(),
|
|
126
|
-
finishReason: finishReason === FinishReason.STOP ? "stop" : finishReason === FinishReason.MAX_TOKENS ? "length" : "content_filter",
|
|
127
|
-
usage: {
|
|
128
|
-
promptTokens: inputTokens,
|
|
129
|
-
completionTokens: outputTokens,
|
|
130
|
-
totalTokens: inputTokens + outputTokens
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
} catch (error) {
|
|
136
|
-
logger.errors("gemini.summarize fatal", {
|
|
137
|
-
error,
|
|
138
|
-
source: "gemini.summarize"
|
|
139
|
-
});
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
getFormatInstructions(style) {
|
|
144
|
-
switch (style) {
|
|
145
|
-
case "bullet-points":
|
|
146
|
-
return "Provide the summary as bullet points.";
|
|
147
|
-
case "concise":
|
|
148
|
-
return "Provide a very brief one or two sentence summary.";
|
|
149
|
-
case "paragraph":
|
|
150
|
-
default:
|
|
151
|
-
return "Provide the summary in paragraph form.";
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
1
|
+
import { ChatStreamSummarizeAdapter } from "@tanstack/ai/adapters";
|
|
2
|
+
import { getGeminiApiKeyFromEnv } from "../utils/client.js";
|
|
3
|
+
import { GeminiTextAdapter } from "./text.js";
|
|
155
4
|
function createGeminiSummarize(apiKey, model, config) {
|
|
156
|
-
return new
|
|
5
|
+
return new ChatStreamSummarizeAdapter(
|
|
6
|
+
new GeminiTextAdapter({ ...config, apiKey }, model),
|
|
7
|
+
model,
|
|
8
|
+
"gemini"
|
|
9
|
+
);
|
|
157
10
|
}
|
|
158
11
|
function geminiSummarize(model, config) {
|
|
159
|
-
|
|
160
|
-
return new GeminiSummarizeAdapter({ ...config, apiKey }, model);
|
|
12
|
+
return createGeminiSummarize(getGeminiApiKeyFromEnv(), model, config);
|
|
161
13
|
}
|
|
162
14
|
export {
|
|
163
|
-
GeminiSummarizeAdapter,
|
|
164
|
-
GeminiSummarizeModels,
|
|
165
15
|
createGeminiSummarize,
|
|
166
16
|
geminiSummarize
|
|
167
17
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summarize.js","sources":["../../../src/adapters/summarize.ts"],"sourcesContent":["import { FinishReason } from '@google/genai'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport type { GoogleGenAI } from '@google/genai'\nimport type { GeminiClientConfig } from '../utils'\nimport type { SummarizeAdapter } from '@tanstack/ai/adapters'\nimport type {\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n} from '@tanstack/ai'\n\n/** Cast an event object to StreamChunk. */\nconst asChunk = (chunk: Record<string, unknown>) =>\n chunk as unknown as StreamChunk\n\n/**\n * Configuration for Gemini summarize adapter\n */\nexport interface GeminiSummarizeConfig extends GeminiClientConfig {}\n/**\n * Available Gemini models for summarization\n */\nexport const GeminiSummarizeModels = [\n 'gemini-3.1-flash-lite-preview',\n 'gemini-2.0-flash',\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-2.0-flash-lite',\n] as const\n\nexport type GeminiSummarizeModel = (typeof GeminiSummarizeModels)[number]\n\n/**\n * Provider-specific options for Gemini summarization\n */\nexport interface GeminiSummarizeProviderOptions {\n /** Generation configuration */\n generationConfig?: {\n temperature?: number\n topP?: number\n topK?: number\n maxOutputTokens?: number\n stopSequences?: Array<string>\n }\n /** Safety settings */\n safetySettings?: Array<{\n category: string\n threshold: string\n }>\n}\n\nexport interface GeminiSummarizeAdapterOptions {\n // Additional adapter options can be added here\n}\n\n/**\n * Gemini Summarize Adapter\n * A tree-shakeable summarization adapter for Google Gemini\n */\nexport class GeminiSummarizeAdapter<\n TModel extends GeminiSummarizeModel,\n> implements SummarizeAdapter<TModel, GeminiSummarizeProviderOptions> {\n readonly kind = 'summarize' as const\n readonly name = 'gemini' as const\n readonly model: TModel\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: GeminiSummarizeProviderOptions\n }\n\n private client: GoogleGenAI\n\n constructor(config: GeminiSummarizeConfig, model: TModel) {\n this.client = createGeminiClient(config)\n this.model = model\n }\n\n async summarize(options: SummarizationOptions): Promise<SummarizationResult> {\n const { logger } = options\n const model = options.model\n\n logger.request(`activity=summarize provider=gemini`, {\n provider: 'gemini',\n model,\n })\n\n // Build the system prompt based on format\n const formatInstructions = this.getFormatInstructions(options.style)\n const lengthInstructions = options.maxLength\n ? ` Keep the summary under ${options.maxLength} tokens.`\n : ''\n\n const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`\n\n try {\n const response = await this.client.models.generateContent({\n model,\n contents: [\n {\n role: 'user',\n parts: [\n { text: `Please summarize the following:\\n\\n${options.text}` },\n ],\n },\n ],\n config: {\n systemInstruction: systemPrompt,\n },\n })\n\n const summary = response.text ?? ''\n const inputTokens = response.usageMetadata?.promptTokenCount ?? 0\n const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0\n\n return {\n id: generateId('sum'),\n model,\n summary,\n usage: {\n promptTokens: inputTokens,\n completionTokens: outputTokens,\n totalTokens: inputTokens + outputTokens,\n },\n }\n } catch (error) {\n logger.errors('gemini.summarize fatal', {\n error,\n source: 'gemini.summarize',\n })\n throw error\n }\n }\n\n async *summarizeStream(\n options: SummarizationOptions,\n ): AsyncIterable<StreamChunk> {\n const { logger } = options\n const model = options.model\n const id = generateId('sum')\n let accumulatedContent = ''\n let inputTokens = 0\n let outputTokens = 0\n\n // Build the system prompt based on format\n const formatInstructions = this.getFormatInstructions(options.style)\n const lengthInstructions = options.maxLength\n ? ` Keep the summary under ${options.maxLength} words.`\n : ''\n\n const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`\n\n logger.request(`activity=summarize provider=gemini`, {\n provider: 'gemini',\n model,\n stream: true,\n })\n\n try {\n const result = await this.client.models.generateContentStream({\n model,\n contents: [\n {\n role: 'user',\n parts: [\n { text: `Please summarize the following:\\n\\n${options.text}` },\n ],\n },\n ],\n config: {\n systemInstruction: systemPrompt,\n },\n })\n\n for await (const chunk of result) {\n logger.provider(`provider=gemini`, { chunk })\n // Track usage metadata\n if (chunk.usageMetadata) {\n inputTokens = chunk.usageMetadata.promptTokenCount ?? inputTokens\n outputTokens =\n chunk.usageMetadata.candidatesTokenCount ?? outputTokens\n }\n\n if (chunk.candidates?.[0]?.content?.parts) {\n for (const part of chunk.candidates[0].content.parts) {\n if (part.text) {\n accumulatedContent += part.text\n yield asChunk({\n type: 'TEXT_MESSAGE_CONTENT',\n messageId: id,\n model,\n timestamp: Date.now(),\n delta: part.text,\n content: accumulatedContent,\n })\n }\n }\n }\n\n // Check for finish reason\n const finishReason = chunk.candidates?.[0]?.finishReason\n if (\n finishReason === FinishReason.STOP ||\n finishReason === FinishReason.MAX_TOKENS ||\n finishReason === FinishReason.SAFETY\n ) {\n yield asChunk({\n type: 'RUN_FINISHED',\n runId: id,\n model,\n timestamp: Date.now(),\n finishReason:\n finishReason === FinishReason.STOP\n ? 'stop'\n : finishReason === FinishReason.MAX_TOKENS\n ? 'length'\n : 'content_filter',\n usage: {\n promptTokens: inputTokens,\n completionTokens: outputTokens,\n totalTokens: inputTokens + outputTokens,\n },\n })\n }\n }\n } catch (error) {\n logger.errors('gemini.summarize fatal', {\n error,\n source: 'gemini.summarize',\n })\n throw error\n }\n }\n\n private getFormatInstructions(\n style?: 'paragraph' | 'bullet-points' | 'concise',\n ): string {\n switch (style) {\n case 'bullet-points':\n return 'Provide the summary as bullet points.'\n case 'concise':\n return 'Provide a very brief one or two sentence summary.'\n case 'paragraph':\n default:\n return 'Provide the summary in paragraph form.'\n }\n }\n}\n\n/**\n * Creates a Gemini summarize adapter with explicit API key and model\n */\nexport function createGeminiSummarize<TModel extends GeminiSummarizeModel>(\n apiKey: string,\n model: TModel,\n config?: Omit<GeminiSummarizeConfig, 'apiKey'>,\n): GeminiSummarizeAdapter<TModel> {\n return new GeminiSummarizeAdapter({ ...config, apiKey }, model)\n}\n\n/**\n * Creates a Gemini summarize adapter with API key from environment and required model\n */\nexport function geminiSummarize<TModel extends GeminiSummarizeModel>(\n model: TModel,\n config?: Omit<GeminiSummarizeConfig, 'apiKey'>,\n): GeminiSummarizeAdapter<TModel> {\n const apiKey = getGeminiApiKeyFromEnv()\n return new GeminiSummarizeAdapter({ ...config, apiKey }, model)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"summarize.js","sources":["../../../src/adapters/summarize.ts"],"sourcesContent":["import { ChatStreamSummarizeAdapter } from '@tanstack/ai/adapters'\nimport { getGeminiApiKeyFromEnv } from '../utils'\nimport { GeminiTextAdapter } from './text'\nimport type { InferTextProviderOptions } from '@tanstack/ai/adapters'\nimport type { GEMINI_MODELS } from '../model-meta'\nimport type { GeminiClientConfig } from '../utils'\n\n/**\n * Configuration for Gemini summarize adapter\n */\nexport interface GeminiSummarizeConfig extends GeminiClientConfig {}\n\nexport type GeminiSummarizeModel = (typeof GEMINI_MODELS)[number]\n\n/**\n * Creates a Gemini summarize adapter with explicit API key and model.\n *\n * Note: keeps the historical (apiKey, model, config) argument order to\n * avoid breaking existing callers.\n *\n * @example\n * ```typescript\n * const adapter = createGeminiSummarize('AIza...', 'gemini-2.0-flash');\n * ```\n */\nexport function createGeminiSummarize<TModel extends GeminiSummarizeModel>(\n apiKey: string,\n model: TModel,\n config?: Omit<GeminiSummarizeConfig, 'apiKey'>,\n): ChatStreamSummarizeAdapter<\n TModel,\n InferTextProviderOptions<GeminiTextAdapter<TModel>>\n> {\n return new ChatStreamSummarizeAdapter(\n new GeminiTextAdapter({ ...config, apiKey }, model),\n model,\n 'gemini',\n )\n}\n\n/**\n * Creates a Gemini summarize adapter with API key from `GOOGLE_API_KEY` /\n * `GEMINI_API_KEY` environment variables.\n *\n * @example\n * ```typescript\n * const adapter = geminiSummarize('gemini-2.0-flash');\n * await summarize({ adapter, text: 'Long article text...' });\n * ```\n */\nexport function geminiSummarize<TModel extends GeminiSummarizeModel>(\n model: TModel,\n config?: Omit<GeminiSummarizeConfig, 'apiKey'>,\n): ChatStreamSummarizeAdapter<\n TModel,\n InferTextProviderOptions<GeminiTextAdapter<TModel>>\n> {\n return createGeminiSummarize(getGeminiApiKeyFromEnv(), model, config)\n}\n"],"names":[],"mappings":";;;AAyBO,SAAS,sBACd,QACA,OACA,QAIA;AACA,SAAO,IAAI;AAAA,IACT,IAAI,kBAAkB,EAAE,GAAG,QAAQ,OAAA,GAAU,KAAK;AAAA,IAClD;AAAA,IACA;AAAA,EAAA;AAEJ;AAYO,SAAS,gBACd,OACA,QAIA;AACA,SAAO,sBAAsB,0BAA0B,OAAO,MAAM;AACtE;"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FinishReason } from "@google/genai";
|
|
2
|
+
import { EventType } from "@tanstack/ai";
|
|
2
3
|
import { BaseTextAdapter } from "@tanstack/ai/adapters";
|
|
3
4
|
import { convertToolsToProviderFormat } from "../tools/tool-converter.js";
|
|
4
5
|
import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "../utils/client.js";
|
|
5
|
-
const asChunk = (chunk) => chunk;
|
|
6
6
|
class GeminiTextAdapter extends BaseTextAdapter {
|
|
7
7
|
constructor(config, model) {
|
|
8
8
|
super({}, model);
|
|
@@ -21,20 +21,19 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
21
21
|
const result = await this.client.models.generateContentStream(mappedOptions);
|
|
22
22
|
yield* this.processStreamChunks(result, options, logger);
|
|
23
23
|
} catch (error) {
|
|
24
|
-
const timestamp = Date.now();
|
|
25
24
|
logger.errors("gemini.chatStream fatal", {
|
|
26
25
|
error,
|
|
27
26
|
source: "gemini.chatStream"
|
|
28
27
|
});
|
|
29
|
-
yield
|
|
30
|
-
type:
|
|
28
|
+
yield {
|
|
29
|
+
type: EventType.RUN_ERROR,
|
|
31
30
|
model: options.model,
|
|
32
|
-
timestamp,
|
|
31
|
+
timestamp: Date.now(),
|
|
33
32
|
message: error instanceof Error ? error.message : "An unknown error occurred during the chat stream.",
|
|
34
33
|
error: {
|
|
35
34
|
message: error instanceof Error ? error.message : "An unknown error occurred during the chat stream."
|
|
36
35
|
}
|
|
37
|
-
}
|
|
36
|
+
};
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
/**
|
|
@@ -98,7 +97,6 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
98
97
|
}
|
|
99
98
|
async *processStreamChunks(result, options, logger) {
|
|
100
99
|
const model = options.model;
|
|
101
|
-
const timestamp = Date.now();
|
|
102
100
|
let accumulatedContent = "";
|
|
103
101
|
let accumulatedThinking = "";
|
|
104
102
|
const toolCallMap = /* @__PURE__ */ new Map();
|
|
@@ -116,13 +114,14 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
116
114
|
logger.provider(`provider=gemini`, { chunk });
|
|
117
115
|
if (!hasEmittedRunStarted) {
|
|
118
116
|
hasEmittedRunStarted = true;
|
|
119
|
-
yield
|
|
120
|
-
type:
|
|
117
|
+
yield {
|
|
118
|
+
type: EventType.RUN_STARTED,
|
|
121
119
|
runId,
|
|
122
120
|
threadId,
|
|
123
121
|
model,
|
|
124
|
-
timestamp
|
|
125
|
-
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
parentRunId: options.parentRunId
|
|
124
|
+
};
|
|
126
125
|
}
|
|
127
126
|
if (chunk.candidates?.[0]?.content?.parts) {
|
|
128
127
|
const parts = chunk.candidates[0].content.parts;
|
|
@@ -133,80 +132,80 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
133
132
|
hasEmittedStepStarted = true;
|
|
134
133
|
stepId = generateId(this.name);
|
|
135
134
|
reasoningMessageId = generateId(this.name);
|
|
136
|
-
yield
|
|
137
|
-
type:
|
|
135
|
+
yield {
|
|
136
|
+
type: EventType.REASONING_START,
|
|
138
137
|
messageId: reasoningMessageId,
|
|
139
138
|
model,
|
|
140
|
-
timestamp
|
|
141
|
-
}
|
|
142
|
-
yield
|
|
143
|
-
type:
|
|
139
|
+
timestamp: Date.now()
|
|
140
|
+
};
|
|
141
|
+
yield {
|
|
142
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
144
143
|
messageId: reasoningMessageId,
|
|
145
144
|
role: "reasoning",
|
|
146
145
|
model,
|
|
147
|
-
timestamp
|
|
148
|
-
}
|
|
149
|
-
yield
|
|
150
|
-
type:
|
|
146
|
+
timestamp: Date.now()
|
|
147
|
+
};
|
|
148
|
+
yield {
|
|
149
|
+
type: EventType.STEP_STARTED,
|
|
151
150
|
stepName: stepId,
|
|
152
151
|
stepId,
|
|
153
152
|
model,
|
|
154
|
-
timestamp,
|
|
153
|
+
timestamp: Date.now(),
|
|
155
154
|
stepType: "thinking"
|
|
156
|
-
}
|
|
155
|
+
};
|
|
157
156
|
}
|
|
158
157
|
accumulatedThinking += part.text;
|
|
159
|
-
yield
|
|
160
|
-
type:
|
|
158
|
+
yield {
|
|
159
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
161
160
|
messageId: reasoningMessageId,
|
|
162
161
|
delta: part.text,
|
|
163
162
|
model,
|
|
164
|
-
timestamp
|
|
165
|
-
}
|
|
166
|
-
yield
|
|
167
|
-
type:
|
|
163
|
+
timestamp: Date.now()
|
|
164
|
+
};
|
|
165
|
+
yield {
|
|
166
|
+
type: EventType.STEP_FINISHED,
|
|
168
167
|
stepName: stepId || generateId(this.name),
|
|
169
168
|
stepId: stepId || generateId(this.name),
|
|
170
169
|
model,
|
|
171
|
-
timestamp,
|
|
170
|
+
timestamp: Date.now(),
|
|
172
171
|
delta: part.text,
|
|
173
172
|
content: accumulatedThinking
|
|
174
|
-
}
|
|
173
|
+
};
|
|
175
174
|
} else if (part.text.trim()) {
|
|
176
175
|
if (reasoningMessageId && !hasClosedReasoning) {
|
|
177
176
|
hasClosedReasoning = true;
|
|
178
|
-
yield
|
|
179
|
-
type:
|
|
177
|
+
yield {
|
|
178
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
180
179
|
messageId: reasoningMessageId,
|
|
181
180
|
model,
|
|
182
|
-
timestamp
|
|
183
|
-
}
|
|
184
|
-
yield
|
|
185
|
-
type:
|
|
181
|
+
timestamp: Date.now()
|
|
182
|
+
};
|
|
183
|
+
yield {
|
|
184
|
+
type: EventType.REASONING_END,
|
|
186
185
|
messageId: reasoningMessageId,
|
|
187
186
|
model,
|
|
188
|
-
timestamp
|
|
189
|
-
}
|
|
187
|
+
timestamp: Date.now()
|
|
188
|
+
};
|
|
190
189
|
}
|
|
191
190
|
if (!hasEmittedTextMessageStart) {
|
|
192
191
|
hasEmittedTextMessageStart = true;
|
|
193
|
-
yield
|
|
194
|
-
type:
|
|
192
|
+
yield {
|
|
193
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
195
194
|
messageId,
|
|
196
195
|
model,
|
|
197
|
-
timestamp,
|
|
196
|
+
timestamp: Date.now(),
|
|
198
197
|
role: "assistant"
|
|
199
|
-
}
|
|
198
|
+
};
|
|
200
199
|
}
|
|
201
200
|
accumulatedContent += part.text;
|
|
202
|
-
yield
|
|
203
|
-
type:
|
|
201
|
+
yield {
|
|
202
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
204
203
|
messageId,
|
|
205
204
|
model,
|
|
206
|
-
timestamp,
|
|
205
|
+
timestamp: Date.now(),
|
|
207
206
|
delta: part.text,
|
|
208
207
|
content: accumulatedContent
|
|
209
|
-
}
|
|
208
|
+
};
|
|
210
209
|
}
|
|
211
210
|
}
|
|
212
211
|
const functionCall = part.functionCall;
|
|
@@ -239,51 +238,51 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
239
238
|
}
|
|
240
239
|
if (!toolCallData.started) {
|
|
241
240
|
toolCallData.started = true;
|
|
242
|
-
yield
|
|
243
|
-
type:
|
|
241
|
+
yield {
|
|
242
|
+
type: EventType.TOOL_CALL_START,
|
|
244
243
|
toolCallId,
|
|
245
244
|
toolCallName: toolCallData.name,
|
|
246
245
|
toolName: toolCallData.name,
|
|
247
246
|
model,
|
|
248
|
-
timestamp,
|
|
247
|
+
timestamp: Date.now(),
|
|
249
248
|
index: toolCallData.index,
|
|
250
249
|
...toolCallData.thoughtSignature && {
|
|
251
250
|
metadata: {
|
|
252
251
|
thoughtSignature: toolCallData.thoughtSignature
|
|
253
252
|
}
|
|
254
253
|
}
|
|
255
|
-
}
|
|
254
|
+
};
|
|
256
255
|
}
|
|
257
|
-
yield
|
|
258
|
-
type:
|
|
256
|
+
yield {
|
|
257
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
259
258
|
toolCallId,
|
|
260
259
|
model,
|
|
261
|
-
timestamp,
|
|
260
|
+
timestamp: Date.now(),
|
|
262
261
|
delta: toolCallData.args,
|
|
263
262
|
args: toolCallData.args
|
|
264
|
-
}
|
|
263
|
+
};
|
|
265
264
|
}
|
|
266
265
|
}
|
|
267
266
|
} else if (chunk.data && chunk.data.trim()) {
|
|
268
267
|
if (!hasEmittedTextMessageStart) {
|
|
269
268
|
hasEmittedTextMessageStart = true;
|
|
270
|
-
yield
|
|
271
|
-
type:
|
|
269
|
+
yield {
|
|
270
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
272
271
|
messageId,
|
|
273
272
|
model,
|
|
274
|
-
timestamp,
|
|
273
|
+
timestamp: Date.now(),
|
|
275
274
|
role: "assistant"
|
|
276
|
-
}
|
|
275
|
+
};
|
|
277
276
|
}
|
|
278
277
|
accumulatedContent += chunk.data;
|
|
279
|
-
yield
|
|
280
|
-
type:
|
|
278
|
+
yield {
|
|
279
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
281
280
|
messageId,
|
|
282
281
|
model,
|
|
283
|
-
timestamp,
|
|
282
|
+
timestamp: Date.now(),
|
|
284
283
|
delta: chunk.data,
|
|
285
284
|
content: accumulatedContent
|
|
286
|
-
}
|
|
285
|
+
};
|
|
287
286
|
}
|
|
288
287
|
if (chunk.candidates?.[0]?.finishReason) {
|
|
289
288
|
const finishReason = chunk.candidates[0].finishReason;
|
|
@@ -301,15 +300,15 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
301
300
|
index: nextToolIndex++,
|
|
302
301
|
started: true
|
|
303
302
|
});
|
|
304
|
-
yield
|
|
305
|
-
type:
|
|
303
|
+
yield {
|
|
304
|
+
type: EventType.TOOL_CALL_START,
|
|
306
305
|
toolCallId,
|
|
307
306
|
toolCallName: functionCall.name || "",
|
|
308
307
|
toolName: functionCall.name || "",
|
|
309
308
|
model,
|
|
310
|
-
timestamp,
|
|
309
|
+
timestamp: Date.now(),
|
|
311
310
|
index: nextToolIndex - 1
|
|
312
|
-
}
|
|
311
|
+
};
|
|
313
312
|
let parsedInput = {};
|
|
314
313
|
try {
|
|
315
314
|
const parsed = typeof functionArgs === "string" ? JSON.parse(functionArgs) : functionArgs;
|
|
@@ -317,15 +316,15 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
317
316
|
} catch {
|
|
318
317
|
parsedInput = {};
|
|
319
318
|
}
|
|
320
|
-
yield
|
|
321
|
-
type:
|
|
319
|
+
yield {
|
|
320
|
+
type: EventType.TOOL_CALL_END,
|
|
322
321
|
toolCallId,
|
|
323
322
|
toolCallName: functionCall.name || "",
|
|
324
323
|
toolName: functionCall.name || "",
|
|
325
324
|
model,
|
|
326
|
-
timestamp,
|
|
325
|
+
timestamp: Date.now(),
|
|
327
326
|
input: parsedInput
|
|
328
|
-
}
|
|
327
|
+
};
|
|
329
328
|
}
|
|
330
329
|
}
|
|
331
330
|
}
|
|
@@ -338,69 +337,69 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
338
337
|
} catch {
|
|
339
338
|
parsedInput = {};
|
|
340
339
|
}
|
|
341
|
-
yield
|
|
342
|
-
type:
|
|
340
|
+
yield {
|
|
341
|
+
type: EventType.TOOL_CALL_END,
|
|
343
342
|
toolCallId,
|
|
344
343
|
toolCallName: toolCallData.name,
|
|
345
344
|
toolName: toolCallData.name,
|
|
346
345
|
model,
|
|
347
|
-
timestamp,
|
|
346
|
+
timestamp: Date.now(),
|
|
348
347
|
input: parsedInput
|
|
349
|
-
}
|
|
348
|
+
};
|
|
350
349
|
}
|
|
351
350
|
if (toolCallMap.size > 0) {
|
|
352
351
|
hasEmittedTextMessageStart = false;
|
|
353
352
|
}
|
|
354
353
|
if (finishReason === FinishReason.MAX_TOKENS) {
|
|
355
|
-
yield
|
|
356
|
-
type:
|
|
354
|
+
yield {
|
|
355
|
+
type: EventType.RUN_ERROR,
|
|
357
356
|
runId,
|
|
358
357
|
model,
|
|
359
|
-
timestamp,
|
|
358
|
+
timestamp: Date.now(),
|
|
360
359
|
message: "The response was cut off because the maximum token limit was reached.",
|
|
361
360
|
code: "max_tokens",
|
|
362
361
|
error: {
|
|
363
362
|
message: "The response was cut off because the maximum token limit was reached.",
|
|
364
363
|
code: "max_tokens"
|
|
365
364
|
}
|
|
366
|
-
}
|
|
365
|
+
};
|
|
367
366
|
}
|
|
368
367
|
if (reasoningMessageId && !hasClosedReasoning) {
|
|
369
368
|
hasClosedReasoning = true;
|
|
370
|
-
yield
|
|
371
|
-
type:
|
|
369
|
+
yield {
|
|
370
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
372
371
|
messageId: reasoningMessageId,
|
|
373
372
|
model,
|
|
374
|
-
timestamp
|
|
375
|
-
}
|
|
376
|
-
yield
|
|
377
|
-
type:
|
|
373
|
+
timestamp: Date.now()
|
|
374
|
+
};
|
|
375
|
+
yield {
|
|
376
|
+
type: EventType.REASONING_END,
|
|
378
377
|
messageId: reasoningMessageId,
|
|
379
378
|
model,
|
|
380
|
-
timestamp
|
|
381
|
-
}
|
|
379
|
+
timestamp: Date.now()
|
|
380
|
+
};
|
|
382
381
|
}
|
|
383
382
|
if (hasEmittedTextMessageStart) {
|
|
384
|
-
yield
|
|
385
|
-
type:
|
|
383
|
+
yield {
|
|
384
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
386
385
|
messageId,
|
|
387
386
|
model,
|
|
388
|
-
timestamp
|
|
389
|
-
}
|
|
387
|
+
timestamp: Date.now()
|
|
388
|
+
};
|
|
390
389
|
}
|
|
391
|
-
yield
|
|
392
|
-
type:
|
|
390
|
+
yield {
|
|
391
|
+
type: EventType.RUN_FINISHED,
|
|
393
392
|
runId,
|
|
394
393
|
threadId,
|
|
395
394
|
model,
|
|
396
|
-
timestamp,
|
|
395
|
+
timestamp: Date.now(),
|
|
397
396
|
finishReason: toolCallMap.size > 0 ? "tool_calls" : "stop",
|
|
398
397
|
usage: chunk.usageMetadata ? {
|
|
399
398
|
promptTokens: chunk.usageMetadata.promptTokenCount ?? 0,
|
|
400
399
|
completionTokens: chunk.usageMetadata.candidatesTokenCount ?? 0,
|
|
401
400
|
totalTokens: chunk.usageMetadata.totalTokenCount ?? 0
|
|
402
401
|
} : void 0
|
|
403
|
-
}
|
|
402
|
+
};
|
|
404
403
|
}
|
|
405
404
|
}
|
|
406
405
|
}
|