@tanstack/ai-gemini 0.10.3 → 0.10.4
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 +93 -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 +93 -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,13 @@ 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
|
+
};
|
|
126
124
|
}
|
|
127
125
|
if (chunk.candidates?.[0]?.content?.parts) {
|
|
128
126
|
const parts = chunk.candidates[0].content.parts;
|
|
@@ -133,80 +131,80 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
133
131
|
hasEmittedStepStarted = true;
|
|
134
132
|
stepId = generateId(this.name);
|
|
135
133
|
reasoningMessageId = generateId(this.name);
|
|
136
|
-
yield
|
|
137
|
-
type:
|
|
134
|
+
yield {
|
|
135
|
+
type: EventType.REASONING_START,
|
|
138
136
|
messageId: reasoningMessageId,
|
|
139
137
|
model,
|
|
140
|
-
timestamp
|
|
141
|
-
}
|
|
142
|
-
yield
|
|
143
|
-
type:
|
|
138
|
+
timestamp: Date.now()
|
|
139
|
+
};
|
|
140
|
+
yield {
|
|
141
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
144
142
|
messageId: reasoningMessageId,
|
|
145
143
|
role: "reasoning",
|
|
146
144
|
model,
|
|
147
|
-
timestamp
|
|
148
|
-
}
|
|
149
|
-
yield
|
|
150
|
-
type:
|
|
145
|
+
timestamp: Date.now()
|
|
146
|
+
};
|
|
147
|
+
yield {
|
|
148
|
+
type: EventType.STEP_STARTED,
|
|
151
149
|
stepName: stepId,
|
|
152
150
|
stepId,
|
|
153
151
|
model,
|
|
154
|
-
timestamp,
|
|
152
|
+
timestamp: Date.now(),
|
|
155
153
|
stepType: "thinking"
|
|
156
|
-
}
|
|
154
|
+
};
|
|
157
155
|
}
|
|
158
156
|
accumulatedThinking += part.text;
|
|
159
|
-
yield
|
|
160
|
-
type:
|
|
157
|
+
yield {
|
|
158
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
161
159
|
messageId: reasoningMessageId,
|
|
162
160
|
delta: part.text,
|
|
163
161
|
model,
|
|
164
|
-
timestamp
|
|
165
|
-
}
|
|
166
|
-
yield
|
|
167
|
-
type:
|
|
162
|
+
timestamp: Date.now()
|
|
163
|
+
};
|
|
164
|
+
yield {
|
|
165
|
+
type: EventType.STEP_FINISHED,
|
|
168
166
|
stepName: stepId || generateId(this.name),
|
|
169
167
|
stepId: stepId || generateId(this.name),
|
|
170
168
|
model,
|
|
171
|
-
timestamp,
|
|
169
|
+
timestamp: Date.now(),
|
|
172
170
|
delta: part.text,
|
|
173
171
|
content: accumulatedThinking
|
|
174
|
-
}
|
|
172
|
+
};
|
|
175
173
|
} else if (part.text.trim()) {
|
|
176
174
|
if (reasoningMessageId && !hasClosedReasoning) {
|
|
177
175
|
hasClosedReasoning = true;
|
|
178
|
-
yield
|
|
179
|
-
type:
|
|
176
|
+
yield {
|
|
177
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
180
178
|
messageId: reasoningMessageId,
|
|
181
179
|
model,
|
|
182
|
-
timestamp
|
|
183
|
-
}
|
|
184
|
-
yield
|
|
185
|
-
type:
|
|
180
|
+
timestamp: Date.now()
|
|
181
|
+
};
|
|
182
|
+
yield {
|
|
183
|
+
type: EventType.REASONING_END,
|
|
186
184
|
messageId: reasoningMessageId,
|
|
187
185
|
model,
|
|
188
|
-
timestamp
|
|
189
|
-
}
|
|
186
|
+
timestamp: Date.now()
|
|
187
|
+
};
|
|
190
188
|
}
|
|
191
189
|
if (!hasEmittedTextMessageStart) {
|
|
192
190
|
hasEmittedTextMessageStart = true;
|
|
193
|
-
yield
|
|
194
|
-
type:
|
|
191
|
+
yield {
|
|
192
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
195
193
|
messageId,
|
|
196
194
|
model,
|
|
197
|
-
timestamp,
|
|
195
|
+
timestamp: Date.now(),
|
|
198
196
|
role: "assistant"
|
|
199
|
-
}
|
|
197
|
+
};
|
|
200
198
|
}
|
|
201
199
|
accumulatedContent += part.text;
|
|
202
|
-
yield
|
|
203
|
-
type:
|
|
200
|
+
yield {
|
|
201
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
204
202
|
messageId,
|
|
205
203
|
model,
|
|
206
|
-
timestamp,
|
|
204
|
+
timestamp: Date.now(),
|
|
207
205
|
delta: part.text,
|
|
208
206
|
content: accumulatedContent
|
|
209
|
-
}
|
|
207
|
+
};
|
|
210
208
|
}
|
|
211
209
|
}
|
|
212
210
|
const functionCall = part.functionCall;
|
|
@@ -239,51 +237,51 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
239
237
|
}
|
|
240
238
|
if (!toolCallData.started) {
|
|
241
239
|
toolCallData.started = true;
|
|
242
|
-
yield
|
|
243
|
-
type:
|
|
240
|
+
yield {
|
|
241
|
+
type: EventType.TOOL_CALL_START,
|
|
244
242
|
toolCallId,
|
|
245
243
|
toolCallName: toolCallData.name,
|
|
246
244
|
toolName: toolCallData.name,
|
|
247
245
|
model,
|
|
248
|
-
timestamp,
|
|
246
|
+
timestamp: Date.now(),
|
|
249
247
|
index: toolCallData.index,
|
|
250
248
|
...toolCallData.thoughtSignature && {
|
|
251
249
|
metadata: {
|
|
252
250
|
thoughtSignature: toolCallData.thoughtSignature
|
|
253
251
|
}
|
|
254
252
|
}
|
|
255
|
-
}
|
|
253
|
+
};
|
|
256
254
|
}
|
|
257
|
-
yield
|
|
258
|
-
type:
|
|
255
|
+
yield {
|
|
256
|
+
type: EventType.TOOL_CALL_ARGS,
|
|
259
257
|
toolCallId,
|
|
260
258
|
model,
|
|
261
|
-
timestamp,
|
|
259
|
+
timestamp: Date.now(),
|
|
262
260
|
delta: toolCallData.args,
|
|
263
261
|
args: toolCallData.args
|
|
264
|
-
}
|
|
262
|
+
};
|
|
265
263
|
}
|
|
266
264
|
}
|
|
267
265
|
} else if (chunk.data && chunk.data.trim()) {
|
|
268
266
|
if (!hasEmittedTextMessageStart) {
|
|
269
267
|
hasEmittedTextMessageStart = true;
|
|
270
|
-
yield
|
|
271
|
-
type:
|
|
268
|
+
yield {
|
|
269
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
272
270
|
messageId,
|
|
273
271
|
model,
|
|
274
|
-
timestamp,
|
|
272
|
+
timestamp: Date.now(),
|
|
275
273
|
role: "assistant"
|
|
276
|
-
}
|
|
274
|
+
};
|
|
277
275
|
}
|
|
278
276
|
accumulatedContent += chunk.data;
|
|
279
|
-
yield
|
|
280
|
-
type:
|
|
277
|
+
yield {
|
|
278
|
+
type: EventType.TEXT_MESSAGE_CONTENT,
|
|
281
279
|
messageId,
|
|
282
280
|
model,
|
|
283
|
-
timestamp,
|
|
281
|
+
timestamp: Date.now(),
|
|
284
282
|
delta: chunk.data,
|
|
285
283
|
content: accumulatedContent
|
|
286
|
-
}
|
|
284
|
+
};
|
|
287
285
|
}
|
|
288
286
|
if (chunk.candidates?.[0]?.finishReason) {
|
|
289
287
|
const finishReason = chunk.candidates[0].finishReason;
|
|
@@ -301,15 +299,15 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
301
299
|
index: nextToolIndex++,
|
|
302
300
|
started: true
|
|
303
301
|
});
|
|
304
|
-
yield
|
|
305
|
-
type:
|
|
302
|
+
yield {
|
|
303
|
+
type: EventType.TOOL_CALL_START,
|
|
306
304
|
toolCallId,
|
|
307
305
|
toolCallName: functionCall.name || "",
|
|
308
306
|
toolName: functionCall.name || "",
|
|
309
307
|
model,
|
|
310
|
-
timestamp,
|
|
308
|
+
timestamp: Date.now(),
|
|
311
309
|
index: nextToolIndex - 1
|
|
312
|
-
}
|
|
310
|
+
};
|
|
313
311
|
let parsedInput = {};
|
|
314
312
|
try {
|
|
315
313
|
const parsed = typeof functionArgs === "string" ? JSON.parse(functionArgs) : functionArgs;
|
|
@@ -317,15 +315,15 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
317
315
|
} catch {
|
|
318
316
|
parsedInput = {};
|
|
319
317
|
}
|
|
320
|
-
yield
|
|
321
|
-
type:
|
|
318
|
+
yield {
|
|
319
|
+
type: EventType.TOOL_CALL_END,
|
|
322
320
|
toolCallId,
|
|
323
321
|
toolCallName: functionCall.name || "",
|
|
324
322
|
toolName: functionCall.name || "",
|
|
325
323
|
model,
|
|
326
|
-
timestamp,
|
|
324
|
+
timestamp: Date.now(),
|
|
327
325
|
input: parsedInput
|
|
328
|
-
}
|
|
326
|
+
};
|
|
329
327
|
}
|
|
330
328
|
}
|
|
331
329
|
}
|
|
@@ -338,69 +336,69 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
338
336
|
} catch {
|
|
339
337
|
parsedInput = {};
|
|
340
338
|
}
|
|
341
|
-
yield
|
|
342
|
-
type:
|
|
339
|
+
yield {
|
|
340
|
+
type: EventType.TOOL_CALL_END,
|
|
343
341
|
toolCallId,
|
|
344
342
|
toolCallName: toolCallData.name,
|
|
345
343
|
toolName: toolCallData.name,
|
|
346
344
|
model,
|
|
347
|
-
timestamp,
|
|
345
|
+
timestamp: Date.now(),
|
|
348
346
|
input: parsedInput
|
|
349
|
-
}
|
|
347
|
+
};
|
|
350
348
|
}
|
|
351
349
|
if (toolCallMap.size > 0) {
|
|
352
350
|
hasEmittedTextMessageStart = false;
|
|
353
351
|
}
|
|
354
352
|
if (finishReason === FinishReason.MAX_TOKENS) {
|
|
355
|
-
yield
|
|
356
|
-
type:
|
|
353
|
+
yield {
|
|
354
|
+
type: EventType.RUN_ERROR,
|
|
357
355
|
runId,
|
|
358
356
|
model,
|
|
359
|
-
timestamp,
|
|
357
|
+
timestamp: Date.now(),
|
|
360
358
|
message: "The response was cut off because the maximum token limit was reached.",
|
|
361
359
|
code: "max_tokens",
|
|
362
360
|
error: {
|
|
363
361
|
message: "The response was cut off because the maximum token limit was reached.",
|
|
364
362
|
code: "max_tokens"
|
|
365
363
|
}
|
|
366
|
-
}
|
|
364
|
+
};
|
|
367
365
|
}
|
|
368
366
|
if (reasoningMessageId && !hasClosedReasoning) {
|
|
369
367
|
hasClosedReasoning = true;
|
|
370
|
-
yield
|
|
371
|
-
type:
|
|
368
|
+
yield {
|
|
369
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
372
370
|
messageId: reasoningMessageId,
|
|
373
371
|
model,
|
|
374
|
-
timestamp
|
|
375
|
-
}
|
|
376
|
-
yield
|
|
377
|
-
type:
|
|
372
|
+
timestamp: Date.now()
|
|
373
|
+
};
|
|
374
|
+
yield {
|
|
375
|
+
type: EventType.REASONING_END,
|
|
378
376
|
messageId: reasoningMessageId,
|
|
379
377
|
model,
|
|
380
|
-
timestamp
|
|
381
|
-
}
|
|
378
|
+
timestamp: Date.now()
|
|
379
|
+
};
|
|
382
380
|
}
|
|
383
381
|
if (hasEmittedTextMessageStart) {
|
|
384
|
-
yield
|
|
385
|
-
type:
|
|
382
|
+
yield {
|
|
383
|
+
type: EventType.TEXT_MESSAGE_END,
|
|
386
384
|
messageId,
|
|
387
385
|
model,
|
|
388
|
-
timestamp
|
|
389
|
-
}
|
|
386
|
+
timestamp: Date.now()
|
|
387
|
+
};
|
|
390
388
|
}
|
|
391
|
-
yield
|
|
392
|
-
type:
|
|
389
|
+
yield {
|
|
390
|
+
type: EventType.RUN_FINISHED,
|
|
393
391
|
runId,
|
|
394
392
|
threadId,
|
|
395
393
|
model,
|
|
396
|
-
timestamp,
|
|
394
|
+
timestamp: Date.now(),
|
|
397
395
|
finishReason: toolCallMap.size > 0 ? "tool_calls" : "stop",
|
|
398
396
|
usage: chunk.usageMetadata ? {
|
|
399
397
|
promptTokens: chunk.usageMetadata.promptTokenCount ?? 0,
|
|
400
398
|
completionTokens: chunk.usageMetadata.candidatesTokenCount ?? 0,
|
|
401
399
|
totalTokens: chunk.usageMetadata.totalTokenCount ?? 0
|
|
402
400
|
} : void 0
|
|
403
|
-
}
|
|
401
|
+
};
|
|
404
402
|
}
|
|
405
403
|
}
|
|
406
404
|
}
|