@tanstack/ai-gemini 0.3.2 → 0.4.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/README.md +1 -0
- package/dist/esm/adapters/summarize.d.ts +9 -4
- package/dist/esm/adapters/summarize.js +11 -12
- package/dist/esm/adapters/summarize.js.map +1 -1
- package/dist/esm/adapters/text.js +134 -59
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/utils/client.d.ts +2 -2
- package/dist/esm/utils/client.js +1 -0
- package/dist/esm/utils/client.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/summarize.ts +15 -19
- package/src/adapters/text.ts +162 -74
- package/src/index.ts +0 -4
- package/src/utils/client.ts +3 -1
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ A powerful, type-safe AI SDK for building AI-powered applications.
|
|
|
45
45
|
- Headless chat state management with adapters (SSE, HTTP stream, custom)
|
|
46
46
|
- Isomorphic type-safe tools with server/client execution
|
|
47
47
|
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions
|
|
48
|
+
- **Observability events** - Structured, typed events for text, tools, image, speech, transcription, and video ([docs](./docs/guides/observability.md))
|
|
48
49
|
|
|
49
50
|
### <a href="https://tanstack.com/ai">Read the docs →</b></a>
|
|
50
51
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GeminiClientConfig } from '../utils.js';
|
|
2
2
|
import { SummarizeAdapter } from '@tanstack/ai/adapters';
|
|
3
3
|
import { StreamChunk, SummarizationOptions, SummarizationResult } from '@tanstack/ai';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for Gemini summarize adapter
|
|
6
|
+
*/
|
|
7
|
+
export interface GeminiSummarizeConfig extends GeminiClientConfig {
|
|
8
|
+
}
|
|
4
9
|
/**
|
|
5
10
|
* Available Gemini models for summarization
|
|
6
11
|
*/
|
|
@@ -38,7 +43,7 @@ export declare class GeminiSummarizeAdapter<TModel extends GeminiSummarizeModel>
|
|
|
38
43
|
providerOptions: GeminiSummarizeProviderOptions;
|
|
39
44
|
};
|
|
40
45
|
private client;
|
|
41
|
-
constructor(
|
|
46
|
+
constructor(config: GeminiSummarizeConfig, model: TModel);
|
|
42
47
|
summarize(options: SummarizationOptions): Promise<SummarizationResult>;
|
|
43
48
|
summarizeStream(options: SummarizationOptions): AsyncIterable<StreamChunk>;
|
|
44
49
|
private getFormatInstructions;
|
|
@@ -46,8 +51,8 @@ export declare class GeminiSummarizeAdapter<TModel extends GeminiSummarizeModel>
|
|
|
46
51
|
/**
|
|
47
52
|
* Creates a Gemini summarize adapter with explicit API key and model
|
|
48
53
|
*/
|
|
49
|
-
export declare function createGeminiSummarize<TModel extends GeminiSummarizeModel>(apiKey: string, model: TModel,
|
|
54
|
+
export declare function createGeminiSummarize<TModel extends GeminiSummarizeModel>(apiKey: string, model: TModel, config?: Omit<GeminiSummarizeConfig, 'apiKey'>): GeminiSummarizeAdapter<TModel>;
|
|
50
55
|
/**
|
|
51
56
|
* Creates a Gemini summarize adapter with API key from environment and required model
|
|
52
57
|
*/
|
|
53
|
-
export declare function geminiSummarize<TModel extends GeminiSummarizeModel>(model: TModel,
|
|
58
|
+
export declare function geminiSummarize<TModel extends GeminiSummarizeModel>(model: TModel, config?: Omit<GeminiSummarizeConfig, 'apiKey'>): GeminiSummarizeAdapter<TModel>;
|
|
@@ -7,10 +7,10 @@ const GeminiSummarizeModels = [
|
|
|
7
7
|
"gemini-2.0-flash-lite"
|
|
8
8
|
];
|
|
9
9
|
class GeminiSummarizeAdapter {
|
|
10
|
-
constructor(
|
|
10
|
+
constructor(config, model) {
|
|
11
11
|
this.kind = "summarize";
|
|
12
12
|
this.name = "gemini";
|
|
13
|
-
this.client =
|
|
13
|
+
this.client = createGeminiClient(config);
|
|
14
14
|
this.model = model;
|
|
15
15
|
}
|
|
16
16
|
async summarize(options) {
|
|
@@ -83,13 +83,12 @@ ${options.text}` }
|
|
|
83
83
|
if (part.text) {
|
|
84
84
|
accumulatedContent += part.text;
|
|
85
85
|
yield {
|
|
86
|
-
type: "
|
|
87
|
-
id,
|
|
86
|
+
type: "TEXT_MESSAGE_CONTENT",
|
|
87
|
+
messageId: id,
|
|
88
88
|
model,
|
|
89
89
|
timestamp: Date.now(),
|
|
90
90
|
delta: part.text,
|
|
91
|
-
content: accumulatedContent
|
|
92
|
-
role: "assistant"
|
|
91
|
+
content: accumulatedContent
|
|
93
92
|
};
|
|
94
93
|
}
|
|
95
94
|
}
|
|
@@ -97,8 +96,8 @@ ${options.text}` }
|
|
|
97
96
|
const finishReason = chunk.candidates?.[0]?.finishReason;
|
|
98
97
|
if (finishReason === FinishReason.STOP || finishReason === FinishReason.MAX_TOKENS || finishReason === FinishReason.SAFETY) {
|
|
99
98
|
yield {
|
|
100
|
-
type: "
|
|
101
|
-
id,
|
|
99
|
+
type: "RUN_FINISHED",
|
|
100
|
+
runId: id,
|
|
102
101
|
model,
|
|
103
102
|
timestamp: Date.now(),
|
|
104
103
|
finishReason: finishReason === FinishReason.STOP ? "stop" : finishReason === FinishReason.MAX_TOKENS ? "length" : "content_filter",
|
|
@@ -123,12 +122,12 @@ ${options.text}` }
|
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
|
-
function createGeminiSummarize(apiKey, model,
|
|
127
|
-
return new GeminiSummarizeAdapter(apiKey, model
|
|
125
|
+
function createGeminiSummarize(apiKey, model, config) {
|
|
126
|
+
return new GeminiSummarizeAdapter({ ...config, apiKey }, model);
|
|
128
127
|
}
|
|
129
|
-
function geminiSummarize(model,
|
|
128
|
+
function geminiSummarize(model, config) {
|
|
130
129
|
const apiKey = getGeminiApiKeyFromEnv();
|
|
131
|
-
return new GeminiSummarizeAdapter(apiKey, model
|
|
130
|
+
return new GeminiSummarizeAdapter({ ...config, apiKey }, model);
|
|
132
131
|
}
|
|
133
132
|
export {
|
|
134
133
|
GeminiSummarizeAdapter,
|
|
@@ -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'\
|
|
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/**\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-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 model = options.model\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 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 }\n\n async *summarizeStream(\n options: SummarizationOptions,\n ): AsyncIterable<StreamChunk> {\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 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 // Track usage metadata\n if (chunk.usageMetadata) {\n inputTokens = chunk.usageMetadata.promptTokenCount ?? inputTokens\n outputTokens = 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 {\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 {\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 }\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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"}
|
|
@@ -17,8 +17,7 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
17
17
|
} catch (error) {
|
|
18
18
|
const timestamp = Date.now();
|
|
19
19
|
yield {
|
|
20
|
-
type: "
|
|
21
|
-
id: generateId(this.name),
|
|
20
|
+
type: "RUN_ERROR",
|
|
22
21
|
model: options.model,
|
|
23
22
|
timestamp,
|
|
24
23
|
error: {
|
|
@@ -82,30 +81,65 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
82
81
|
let accumulatedContent = "";
|
|
83
82
|
const toolCallMap = /* @__PURE__ */ new Map();
|
|
84
83
|
let nextToolIndex = 0;
|
|
84
|
+
const runId = generateId(this.name);
|
|
85
|
+
const messageId = generateId(this.name);
|
|
86
|
+
let stepId = null;
|
|
87
|
+
let hasEmittedRunStarted = false;
|
|
88
|
+
let hasEmittedTextMessageStart = false;
|
|
89
|
+
let hasEmittedStepStarted = false;
|
|
85
90
|
for await (const chunk of result) {
|
|
91
|
+
if (!hasEmittedRunStarted) {
|
|
92
|
+
hasEmittedRunStarted = true;
|
|
93
|
+
yield {
|
|
94
|
+
type: "RUN_STARTED",
|
|
95
|
+
runId,
|
|
96
|
+
model,
|
|
97
|
+
timestamp
|
|
98
|
+
};
|
|
99
|
+
}
|
|
86
100
|
if (chunk.candidates?.[0]?.content?.parts) {
|
|
87
101
|
const parts = chunk.candidates[0].content.parts;
|
|
88
102
|
for (const part of parts) {
|
|
89
103
|
if (part.text) {
|
|
90
104
|
if (part.thought) {
|
|
105
|
+
if (!hasEmittedStepStarted) {
|
|
106
|
+
hasEmittedStepStarted = true;
|
|
107
|
+
stepId = generateId(this.name);
|
|
108
|
+
yield {
|
|
109
|
+
type: "STEP_STARTED",
|
|
110
|
+
stepId,
|
|
111
|
+
model,
|
|
112
|
+
timestamp,
|
|
113
|
+
stepType: "thinking"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
91
116
|
yield {
|
|
92
|
-
type: "
|
|
93
|
-
|
|
94
|
-
delta: part.text,
|
|
95
|
-
id: generateId(this.name),
|
|
117
|
+
type: "STEP_FINISHED",
|
|
118
|
+
stepId: stepId || generateId(this.name),
|
|
96
119
|
model,
|
|
97
|
-
timestamp
|
|
120
|
+
timestamp,
|
|
121
|
+
delta: part.text,
|
|
122
|
+
content: part.text
|
|
98
123
|
};
|
|
99
124
|
} else {
|
|
125
|
+
if (!hasEmittedTextMessageStart) {
|
|
126
|
+
hasEmittedTextMessageStart = true;
|
|
127
|
+
yield {
|
|
128
|
+
type: "TEXT_MESSAGE_START",
|
|
129
|
+
messageId,
|
|
130
|
+
model,
|
|
131
|
+
timestamp,
|
|
132
|
+
role: "assistant"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
100
135
|
accumulatedContent += part.text;
|
|
101
136
|
yield {
|
|
102
|
-
type: "
|
|
103
|
-
|
|
137
|
+
type: "TEXT_MESSAGE_CONTENT",
|
|
138
|
+
messageId,
|
|
104
139
|
model,
|
|
105
140
|
timestamp,
|
|
106
141
|
delta: part.text,
|
|
107
|
-
content: accumulatedContent
|
|
108
|
-
role: "assistant"
|
|
142
|
+
content: accumulatedContent
|
|
109
143
|
};
|
|
110
144
|
}
|
|
111
145
|
}
|
|
@@ -118,7 +152,8 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
118
152
|
toolCallData = {
|
|
119
153
|
name: functionCall.name || "",
|
|
120
154
|
args: typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs),
|
|
121
|
-
index: nextToolIndex
|
|
155
|
+
index: nextToolIndex++,
|
|
156
|
+
started: false
|
|
122
157
|
};
|
|
123
158
|
toolCallMap.set(toolCallId, toolCallData);
|
|
124
159
|
} else {
|
|
@@ -131,33 +166,46 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
131
166
|
toolCallData.args = typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs);
|
|
132
167
|
}
|
|
133
168
|
}
|
|
169
|
+
if (!toolCallData.started) {
|
|
170
|
+
toolCallData.started = true;
|
|
171
|
+
yield {
|
|
172
|
+
type: "TOOL_CALL_START",
|
|
173
|
+
toolCallId,
|
|
174
|
+
toolName: toolCallData.name,
|
|
175
|
+
model,
|
|
176
|
+
timestamp,
|
|
177
|
+
index: toolCallData.index
|
|
178
|
+
};
|
|
179
|
+
}
|
|
134
180
|
yield {
|
|
135
|
-
type: "
|
|
136
|
-
|
|
181
|
+
type: "TOOL_CALL_ARGS",
|
|
182
|
+
toolCallId,
|
|
137
183
|
model,
|
|
138
184
|
timestamp,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
type: "function",
|
|
142
|
-
function: {
|
|
143
|
-
name: toolCallData.name,
|
|
144
|
-
arguments: toolCallData.args
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
index: toolCallData.index
|
|
185
|
+
delta: toolCallData.args,
|
|
186
|
+
args: toolCallData.args
|
|
148
187
|
};
|
|
149
188
|
}
|
|
150
189
|
}
|
|
151
190
|
} else if (chunk.data) {
|
|
191
|
+
if (!hasEmittedTextMessageStart) {
|
|
192
|
+
hasEmittedTextMessageStart = true;
|
|
193
|
+
yield {
|
|
194
|
+
type: "TEXT_MESSAGE_START",
|
|
195
|
+
messageId,
|
|
196
|
+
model,
|
|
197
|
+
timestamp,
|
|
198
|
+
role: "assistant"
|
|
199
|
+
};
|
|
200
|
+
}
|
|
152
201
|
accumulatedContent += chunk.data;
|
|
153
202
|
yield {
|
|
154
|
-
type: "
|
|
155
|
-
|
|
203
|
+
type: "TEXT_MESSAGE_CONTENT",
|
|
204
|
+
messageId,
|
|
156
205
|
model,
|
|
157
206
|
timestamp,
|
|
158
207
|
delta: chunk.data,
|
|
159
|
-
content: accumulatedContent
|
|
160
|
-
role: "assistant"
|
|
208
|
+
content: accumulatedContent
|
|
161
209
|
};
|
|
162
210
|
}
|
|
163
211
|
if (chunk.candidates?.[0]?.finishReason) {
|
|
@@ -169,44 +217,78 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
169
217
|
if (functionCall) {
|
|
170
218
|
const toolCallId = functionCall.id || `${functionCall.name}_${Date.now()}_${nextToolIndex}`;
|
|
171
219
|
const functionArgs = functionCall.args || {};
|
|
220
|
+
const argsString = typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs);
|
|
172
221
|
toolCallMap.set(toolCallId, {
|
|
173
222
|
name: functionCall.name || "",
|
|
174
|
-
args:
|
|
175
|
-
index: nextToolIndex
|
|
223
|
+
args: argsString,
|
|
224
|
+
index: nextToolIndex++,
|
|
225
|
+
started: true
|
|
176
226
|
});
|
|
177
227
|
yield {
|
|
178
|
-
type: "
|
|
179
|
-
|
|
228
|
+
type: "TOOL_CALL_START",
|
|
229
|
+
toolCallId,
|
|
230
|
+
toolName: functionCall.name || "",
|
|
180
231
|
model,
|
|
181
232
|
timestamp,
|
|
182
|
-
toolCall: {
|
|
183
|
-
id: toolCallId,
|
|
184
|
-
type: "function",
|
|
185
|
-
function: {
|
|
186
|
-
name: functionCall.name || "",
|
|
187
|
-
arguments: typeof functionArgs === "string" ? functionArgs : JSON.stringify(functionArgs)
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
233
|
index: nextToolIndex - 1
|
|
191
234
|
};
|
|
235
|
+
let parsedInput = {};
|
|
236
|
+
try {
|
|
237
|
+
parsedInput = typeof functionArgs === "string" ? JSON.parse(functionArgs) : functionArgs;
|
|
238
|
+
} catch {
|
|
239
|
+
parsedInput = {};
|
|
240
|
+
}
|
|
241
|
+
yield {
|
|
242
|
+
type: "TOOL_CALL_END",
|
|
243
|
+
toolCallId,
|
|
244
|
+
toolName: functionCall.name || "",
|
|
245
|
+
model,
|
|
246
|
+
timestamp,
|
|
247
|
+
input: parsedInput
|
|
248
|
+
};
|
|
192
249
|
}
|
|
193
250
|
}
|
|
194
251
|
}
|
|
195
252
|
}
|
|
253
|
+
for (const [toolCallId, toolCallData] of toolCallMap.entries()) {
|
|
254
|
+
let parsedInput = {};
|
|
255
|
+
try {
|
|
256
|
+
parsedInput = JSON.parse(toolCallData.args);
|
|
257
|
+
} catch {
|
|
258
|
+
parsedInput = {};
|
|
259
|
+
}
|
|
260
|
+
yield {
|
|
261
|
+
type: "TOOL_CALL_END",
|
|
262
|
+
toolCallId,
|
|
263
|
+
toolName: toolCallData.name,
|
|
264
|
+
model,
|
|
265
|
+
timestamp,
|
|
266
|
+
input: parsedInput
|
|
267
|
+
};
|
|
268
|
+
}
|
|
196
269
|
if (finishReason === FinishReason.MAX_TOKENS) {
|
|
197
270
|
yield {
|
|
198
|
-
type: "
|
|
199
|
-
|
|
271
|
+
type: "RUN_ERROR",
|
|
272
|
+
runId,
|
|
200
273
|
model,
|
|
201
274
|
timestamp,
|
|
202
275
|
error: {
|
|
203
|
-
message: "The response was cut off because the maximum token limit was reached."
|
|
276
|
+
message: "The response was cut off because the maximum token limit was reached.",
|
|
277
|
+
code: "max_tokens"
|
|
204
278
|
}
|
|
205
279
|
};
|
|
206
280
|
}
|
|
281
|
+
if (hasEmittedTextMessageStart) {
|
|
282
|
+
yield {
|
|
283
|
+
type: "TEXT_MESSAGE_END",
|
|
284
|
+
messageId,
|
|
285
|
+
model,
|
|
286
|
+
timestamp
|
|
287
|
+
};
|
|
288
|
+
}
|
|
207
289
|
yield {
|
|
208
|
-
type: "
|
|
209
|
-
|
|
290
|
+
type: "RUN_FINISHED",
|
|
291
|
+
runId,
|
|
210
292
|
model,
|
|
211
293
|
timestamp,
|
|
212
294
|
finishReason: toolCallMap.size > 0 ? "tool_calls" : "stop",
|
|
@@ -220,18 +302,6 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
220
302
|
}
|
|
221
303
|
}
|
|
222
304
|
convertContentPartToGemini(part) {
|
|
223
|
-
const getDefaultFileType = (part2) => {
|
|
224
|
-
switch (part2) {
|
|
225
|
-
case "image":
|
|
226
|
-
return "image/jpeg";
|
|
227
|
-
case "audio":
|
|
228
|
-
return "audio/mp3";
|
|
229
|
-
case "video":
|
|
230
|
-
return "video/mp4";
|
|
231
|
-
case "document":
|
|
232
|
-
return "application/pdf";
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
305
|
switch (part.type) {
|
|
236
306
|
case "text":
|
|
237
307
|
return { text: part.content };
|
|
@@ -239,19 +309,24 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
239
309
|
case "audio":
|
|
240
310
|
case "video":
|
|
241
311
|
case "document": {
|
|
242
|
-
const metadata = part.metadata;
|
|
243
312
|
if (part.source.type === "data") {
|
|
244
313
|
return {
|
|
245
314
|
inlineData: {
|
|
246
315
|
data: part.source.value,
|
|
247
|
-
mimeType:
|
|
316
|
+
mimeType: part.source.mimeType
|
|
248
317
|
}
|
|
249
318
|
};
|
|
250
319
|
} else {
|
|
320
|
+
const defaultMimeType = {
|
|
321
|
+
image: "image/jpeg",
|
|
322
|
+
audio: "audio/mp3",
|
|
323
|
+
video: "video/mp4",
|
|
324
|
+
document: "application/pdf"
|
|
325
|
+
}[part.type];
|
|
251
326
|
return {
|
|
252
327
|
fileData: {
|
|
253
328
|
fileUri: part.source.value,
|
|
254
|
-
mimeType:
|
|
329
|
+
mimeType: part.source.mimeType ?? defaultMimeType
|
|
255
330
|
}
|
|
256
331
|
};
|
|
257
332
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { FinishReason } from '@google/genai'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { convertToolsToProviderFormat } from '../tools/tool-converter'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport type {\n GEMINI_MODELS,\n GeminiChatModelProviderOptionsByName,\n GeminiModelInputModalitiesByName,\n} from '../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n GenerateContentParameters,\n GenerateContentResponse,\n GoogleGenAI,\n Part,\n ThinkingLevel,\n} from '@google/genai'\nimport type {\n ContentPart,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { ExternalTextProviderOptions } from '../text/text-provider-options'\nimport type {\n GeminiAudioMetadata,\n GeminiDocumentMetadata,\n GeminiImageMetadata,\n GeminiMessageMetadataByModality,\n GeminiVideoMetadata,\n} from '../message-types'\nimport type { GeminiClientConfig } from '../utils'\n\n/**\n * Configuration for Gemini text adapter\n */\nexport interface GeminiTextConfig extends GeminiClientConfig {}\n\n/**\n * Gemini-specific provider options for text/chat\n */\nexport type GeminiTextProviderOptions = ExternalTextProviderOptions\n\n// ===========================\n// Type Resolution Helpers\n// ===========================\n\n/**\n * Resolve provider options for a specific model.\n * If the model has explicit options in the map, use those; otherwise use base options.\n */\ntype ResolveProviderOptions<TModel extends string> =\n TModel extends keyof GeminiChatModelProviderOptionsByName\n ? GeminiChatModelProviderOptionsByName[TModel]\n : GeminiTextProviderOptions\n\n/**\n * Resolve input modalities for a specific model.\n * If the model has explicit modalities in the map, use those; otherwise use all modalities.\n */\ntype ResolveInputModalities<TModel extends string> =\n TModel extends keyof GeminiModelInputModalitiesByName\n ? GeminiModelInputModalitiesByName[TModel]\n : readonly ['text', 'image', 'audio', 'video', 'document']\n\n// ===========================\n// Adapter Implementation\n// ===========================\n\n/**\n * Gemini Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Gemini chat/text completion functionality.\n * Import only what you need for smaller bundle sizes.\n */\nexport class GeminiTextAdapter<\n TModel extends (typeof GEMINI_MODELS)[number],\n TProviderOptions extends object = ResolveProviderOptions<TModel>,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n GeminiMessageMetadataByModality\n> {\n readonly kind = 'text' as const\n readonly name = 'gemini' as const\n\n private client: GoogleGenAI\n\n constructor(config: GeminiTextConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n }\n\n async *chatStream(\n options: TextOptions<GeminiTextProviderOptions>,\n ): AsyncIterable<StreamChunk> {\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: generateId(this.name),\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 /**\n * Generate structured output using Gemini's native JSON response format.\n * Uses responseMimeType: 'application/json' and responseSchema for structured output.\n * The outputSchema is already JSON Schema (converted in the ai layer).\n */\n async structuredOutput(\n options: StructuredOutputOptions<GeminiTextProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n\n const mappedOptions = this.mapCommonOptionsToGemini(chatOptions)\n\n try {\n // Add structured output configuration\n const result = await this.client.models.generateContent({\n ...mappedOptions,\n config: {\n ...mappedOptions.config,\n responseMimeType: 'application/json',\n responseSchema: outputSchema,\n },\n })\n\n // Extract text content from the response\n const rawText = this.extractTextFromResponse(result)\n\n // Parse the JSON response\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n return {\n data: parsed,\n rawText,\n }\n } catch (error) {\n throw new Error(\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during structured output generation.',\n )\n }\n }\n\n /**\n * Extract text content from a non-streaming response\n */\n private extractTextFromResponse(response: GenerateContentResponse): string {\n let textContent = ''\n\n if (response.candidates?.[0]?.content?.parts) {\n for (const part of response.candidates[0].content.parts) {\n if (part.text) {\n textContent += part.text\n }\n }\n }\n\n return textContent\n }\n\n private async *processStreamChunks(\n result: AsyncGenerator<GenerateContentResponse, unknown, unknown>,\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\n for await (const chunk of result) {\n if (chunk.candidates?.[0]?.content?.parts) {\n const parts = chunk.candidates[0].content.parts\n\n for (const part of parts) {\n if (part.text) {\n if (part.thought) {\n yield {\n type: 'thinking',\n content: part.text,\n delta: part.text,\n id: generateId(this.name),\n model,\n timestamp,\n }\n } else {\n accumulatedContent += part.text\n yield {\n type: 'content',\n id: generateId(this.name),\n model,\n timestamp,\n delta: part.text,\n content: accumulatedContent,\n role: 'assistant',\n }\n }\n }\n\n const functionCall = part.functionCall\n if (functionCall) {\n const toolCallId =\n functionCall.id ||\n `${functionCall.name}_${Date.now()}_${nextToolIndex}`\n const functionArgs = functionCall.args || {}\n\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 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 toolCallData.args =\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs)\n }\n }\n\n yield {\n type: 'tool_call',\n id: generateId(this.name),\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 accumulatedContent += chunk.data\n yield {\n type: 'content',\n id: generateId(this.name),\n model,\n timestamp,\n delta: chunk.data,\n content: accumulatedContent,\n role: 'assistant',\n }\n }\n\n if (chunk.candidates?.[0]?.finishReason) {\n const finishReason = chunk.candidates[0].finishReason\n\n if (finishReason === FinishReason.UNEXPECTED_TOOL_CALL) {\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 const toolCallId =\n functionCall.id ||\n `${functionCall.name}_${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: generateId(this.name),\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: generateId(this.name),\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: generateId(this.name),\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.candidatesTokenCount ?? 0,\n totalTokens: chunk.usageMetadata.totalTokenCount ?? 0,\n }\n : undefined,\n }\n }\n }\n }\n\n private convertContentPartToGemini(part: ContentPart): Part {\n const getDefaultFileType = (\n part: 'image' | 'audio' | 'video' | 'document',\n ) => {\n switch (part) {\n case 'image':\n return 'image/jpeg'\n case 'audio':\n return 'audio/mp3'\n case 'video':\n return 'video/mp4'\n case 'document':\n return 'application/pdf'\n }\n }\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 if (part.source.type === 'data') {\n return {\n inlineData: {\n data: part.source.value,\n mimeType: metadata?.mimeType ?? getDefaultFileType(part.type),\n },\n }\n } else {\n return {\n fileData: {\n fileUri: part.source.value,\n mimeType: metadata?.mimeType ?? getDefaultFileType(part.type),\n },\n }\n }\n }\n default: {\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 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 parts.push({ text: msg.content })\n }\n\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) as Record<\n string,\n unknown\n >)\n : {}\n } catch {\n parsedArgs = toolCall.function.arguments as unknown as Record<\n string,\n unknown\n >\n }\n\n parts.push({\n functionCall: {\n name: toolCall.function.name,\n args: parsedArgs,\n },\n })\n }\n }\n\n if (msg.role === 'tool' && msg.toolCallId) {\n parts.push({\n functionResponse: {\n name: msg.toolCallId,\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 private mapCommonOptionsToGemini(\n options: TextOptions<GeminiTextProviderOptions>,\n ) {\n const modelOpts = options.modelOptions\n const thinkingConfig = modelOpts?.thinkingConfig\n const requestOptions: GenerateContentParameters = {\n model: options.model,\n contents: this.formatMessages(options.messages),\n config: {\n ...modelOpts,\n temperature: options.temperature,\n topP: options.topP,\n maxOutputTokens: options.maxTokens,\n thinkingConfig: thinkingConfig\n ? {\n ...thinkingConfig,\n thinkingLevel: thinkingConfig.thinkingLevel\n ? // Enum is provided by the SDK, we use it for the type but cast it to string constants, here we just cast them back\n (thinkingConfig.thinkingLevel as ThinkingLevel)\n : undefined,\n }\n : undefined,\n systemInstruction: options.systemPrompts?.join('\\n'),\n tools: convertToolsToProviderFormat(options.tools),\n },\n }\n\n return requestOptions\n }\n}\n\n/**\n * Creates a Gemini text adapter with explicit API key.\n * Type resolution happens here at the call site.\n */\nexport function createGeminiChat<TModel extends (typeof GEMINI_MODELS)[number]>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiTextConfig, 'apiKey'>,\n): GeminiTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>\n> {\n return new GeminiTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Gemini text adapter with automatic API key detection.\n * Type resolution happens here at the call site.\n */\nexport function geminiText<TModel extends (typeof GEMINI_MODELS)[number]>(\n model: TModel,\n config?: Omit<GeminiTextConfig, 'apiKey'>,\n): GeminiTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>\n> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiChat(model, apiKey, config)\n}\n"],"names":["part"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"text.js","sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { FinishReason } from '@google/genai'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { convertToolsToProviderFormat } from '../tools/tool-converter'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport type {\n GEMINI_MODELS,\n GeminiChatModelProviderOptionsByName,\n GeminiModelInputModalitiesByName,\n} from '../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n GenerateContentParameters,\n GenerateContentResponse,\n GoogleGenAI,\n Part,\n ThinkingLevel,\n} from '@google/genai'\nimport type {\n ContentPart,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { ExternalTextProviderOptions } from '../text/text-provider-options'\nimport type { GeminiMessageMetadataByModality } from '../message-types'\nimport type { GeminiClientConfig } from '../utils'\n\n/**\n * Configuration for Gemini text adapter\n */\nexport interface GeminiTextConfig extends GeminiClientConfig {}\n\n/**\n * Gemini-specific provider options for text/chat\n */\nexport type GeminiTextProviderOptions = ExternalTextProviderOptions\n\n// ===========================\n// Type Resolution Helpers\n// ===========================\n\n/**\n * Resolve provider options for a specific model.\n * If the model has explicit options in the map, use those; otherwise use base options.\n */\ntype ResolveProviderOptions<TModel extends string> =\n TModel extends keyof GeminiChatModelProviderOptionsByName\n ? GeminiChatModelProviderOptionsByName[TModel]\n : GeminiTextProviderOptions\n\n/**\n * Resolve input modalities for a specific model.\n * If the model has explicit modalities in the map, use those; otherwise use all modalities.\n */\ntype ResolveInputModalities<TModel extends string> =\n TModel extends keyof GeminiModelInputModalitiesByName\n ? GeminiModelInputModalitiesByName[TModel]\n : readonly ['text', 'image', 'audio', 'video', 'document']\n\n// ===========================\n// Adapter Implementation\n// ===========================\n\n/**\n * Gemini Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Gemini chat/text completion functionality.\n * Import only what you need for smaller bundle sizes.\n */\nexport class GeminiTextAdapter<\n TModel extends (typeof GEMINI_MODELS)[number],\n TProviderOptions extends object = ResolveProviderOptions<TModel>,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n GeminiMessageMetadataByModality\n> {\n readonly kind = 'text' as const\n readonly name = 'gemini' as const\n\n private client: GoogleGenAI\n\n constructor(config: GeminiTextConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n }\n\n async *chatStream(\n options: TextOptions<GeminiTextProviderOptions>,\n ): AsyncIterable<StreamChunk> {\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: 'RUN_ERROR',\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 /**\n * Generate structured output using Gemini's native JSON response format.\n * Uses responseMimeType: 'application/json' and responseSchema for structured output.\n * The outputSchema is already JSON Schema (converted in the ai layer).\n */\n async structuredOutput(\n options: StructuredOutputOptions<GeminiTextProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n\n const mappedOptions = this.mapCommonOptionsToGemini(chatOptions)\n\n try {\n // Add structured output configuration\n const result = await this.client.models.generateContent({\n ...mappedOptions,\n config: {\n ...mappedOptions.config,\n responseMimeType: 'application/json',\n responseSchema: outputSchema,\n },\n })\n\n // Extract text content from the response\n const rawText = this.extractTextFromResponse(result)\n\n // Parse the JSON response\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n return {\n data: parsed,\n rawText,\n }\n } catch (error) {\n throw new Error(\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during structured output generation.',\n )\n }\n }\n\n /**\n * Extract text content from a non-streaming response\n */\n private extractTextFromResponse(response: GenerateContentResponse): string {\n let textContent = ''\n\n if (response.candidates?.[0]?.content?.parts) {\n for (const part of response.candidates[0].content.parts) {\n if (part.text) {\n textContent += part.text\n }\n }\n }\n\n return textContent\n }\n\n private async *processStreamChunks(\n result: AsyncGenerator<GenerateContentResponse, unknown, unknown>,\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; started: boolean }\n >()\n let nextToolIndex = 0\n\n // AG-UI lifecycle tracking\n const runId = generateId(this.name)\n const messageId = generateId(this.name)\n let stepId: string | null = null\n let hasEmittedRunStarted = false\n let hasEmittedTextMessageStart = false\n let hasEmittedStepStarted = false\n\n for await (const chunk of result) {\n // Emit RUN_STARTED on first chunk\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: 'RUN_STARTED',\n runId,\n model,\n timestamp,\n }\n }\n\n if (chunk.candidates?.[0]?.content?.parts) {\n const parts = chunk.candidates[0].content.parts\n\n for (const part of parts) {\n if (part.text) {\n if (part.thought) {\n // Emit STEP_STARTED on first thinking content\n if (!hasEmittedStepStarted) {\n hasEmittedStepStarted = true\n stepId = generateId(this.name)\n yield {\n type: 'STEP_STARTED',\n stepId,\n model,\n timestamp,\n stepType: 'thinking',\n }\n }\n\n yield {\n type: 'STEP_FINISHED',\n stepId: stepId || generateId(this.name),\n model,\n timestamp,\n delta: part.text,\n content: part.text,\n }\n } else {\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: 'TEXT_MESSAGE_START',\n messageId,\n model,\n timestamp,\n role: 'assistant',\n }\n }\n\n accumulatedContent += part.text\n yield {\n type: 'TEXT_MESSAGE_CONTENT',\n messageId,\n model,\n timestamp,\n delta: part.text,\n content: accumulatedContent,\n }\n }\n }\n\n const functionCall = part.functionCall\n if (functionCall) {\n const toolCallId =\n functionCall.id ||\n `${functionCall.name}_${Date.now()}_${nextToolIndex}`\n const functionArgs = functionCall.args || {}\n\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 started: false,\n }\n toolCallMap.set(toolCallId, toolCallData)\n } else {\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 toolCallData.args =\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs)\n }\n }\n\n // Emit TOOL_CALL_START if not already started\n if (!toolCallData.started) {\n toolCallData.started = true\n yield {\n type: 'TOOL_CALL_START',\n toolCallId,\n toolName: toolCallData.name,\n model,\n timestamp,\n index: toolCallData.index,\n }\n }\n\n // Emit TOOL_CALL_ARGS\n yield {\n type: 'TOOL_CALL_ARGS',\n toolCallId,\n model,\n timestamp,\n delta: toolCallData.args,\n args: toolCallData.args,\n }\n }\n }\n } else if (chunk.data) {\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: 'TEXT_MESSAGE_START',\n messageId,\n model,\n timestamp,\n role: 'assistant',\n }\n }\n\n accumulatedContent += chunk.data\n yield {\n type: 'TEXT_MESSAGE_CONTENT',\n messageId,\n model,\n timestamp,\n delta: chunk.data,\n content: accumulatedContent,\n }\n }\n\n if (chunk.candidates?.[0]?.finishReason) {\n const finishReason = chunk.candidates[0].finishReason\n\n if (finishReason === FinishReason.UNEXPECTED_TOOL_CALL) {\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 const toolCallId =\n functionCall.id ||\n `${functionCall.name}_${Date.now()}_${nextToolIndex}`\n const functionArgs = functionCall.args || {}\n\n const argsString =\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs)\n\n toolCallMap.set(toolCallId, {\n name: functionCall.name || '',\n args: argsString,\n index: nextToolIndex++,\n started: true,\n })\n\n // Emit TOOL_CALL_START\n yield {\n type: 'TOOL_CALL_START',\n toolCallId,\n toolName: functionCall.name || '',\n model,\n timestamp,\n index: nextToolIndex - 1,\n }\n\n // Emit TOOL_CALL_END with parsed input\n let parsedInput: unknown = {}\n try {\n parsedInput =\n typeof functionArgs === 'string'\n ? JSON.parse(functionArgs)\n : functionArgs\n } catch {\n parsedInput = {}\n }\n\n yield {\n type: 'TOOL_CALL_END',\n toolCallId,\n toolName: functionCall.name || '',\n model,\n timestamp,\n input: parsedInput,\n }\n }\n }\n }\n }\n\n // Emit TOOL_CALL_END for all tracked tool calls\n for (const [toolCallId, toolCallData] of toolCallMap.entries()) {\n let parsedInput: unknown = {}\n try {\n parsedInput = JSON.parse(toolCallData.args)\n } catch {\n parsedInput = {}\n }\n\n yield {\n type: 'TOOL_CALL_END',\n toolCallId,\n toolName: toolCallData.name,\n model,\n timestamp,\n input: parsedInput,\n }\n }\n\n if (finishReason === FinishReason.MAX_TOKENS) {\n yield {\n type: 'RUN_ERROR',\n runId,\n model,\n timestamp,\n error: {\n message:\n 'The response was cut off because the maximum token limit was reached.',\n code: 'max_tokens',\n },\n }\n }\n\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: 'TEXT_MESSAGE_END',\n messageId,\n model,\n timestamp,\n }\n }\n\n yield {\n type: 'RUN_FINISHED',\n runId,\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.candidatesTokenCount ?? 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 if (part.source.type === 'data') {\n return {\n inlineData: {\n data: part.source.value,\n mimeType: part.source.mimeType,\n },\n }\n } else {\n // For URL sources, use provided mimeType or fall back to reasonable defaults\n const defaultMimeType = {\n image: 'image/jpeg',\n audio: 'audio/mp3',\n video: 'video/mp4',\n document: 'application/pdf',\n }[part.type]\n\n return {\n fileData: {\n fileUri: part.source.value,\n mimeType: part.source.mimeType ?? defaultMimeType,\n },\n }\n }\n }\n default: {\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 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 parts.push({ text: msg.content })\n }\n\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) as Record<\n string,\n unknown\n >)\n : {}\n } catch {\n parsedArgs = toolCall.function.arguments as unknown as Record<\n string,\n unknown\n >\n }\n\n parts.push({\n functionCall: {\n name: toolCall.function.name,\n args: parsedArgs,\n },\n })\n }\n }\n\n if (msg.role === 'tool' && msg.toolCallId) {\n parts.push({\n functionResponse: {\n name: msg.toolCallId,\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 private mapCommonOptionsToGemini(\n options: TextOptions<GeminiTextProviderOptions>,\n ) {\n const modelOpts = options.modelOptions\n const thinkingConfig = modelOpts?.thinkingConfig\n const requestOptions: GenerateContentParameters = {\n model: options.model,\n contents: this.formatMessages(options.messages),\n config: {\n ...modelOpts,\n temperature: options.temperature,\n topP: options.topP,\n maxOutputTokens: options.maxTokens,\n thinkingConfig: thinkingConfig\n ? {\n ...thinkingConfig,\n thinkingLevel: thinkingConfig.thinkingLevel\n ? // Enum is provided by the SDK, we use it for the type but cast it to string constants, here we just cast them back\n (thinkingConfig.thinkingLevel as ThinkingLevel)\n : undefined,\n }\n : undefined,\n systemInstruction: options.systemPrompts?.join('\\n'),\n tools: convertToolsToProviderFormat(options.tools),\n },\n }\n\n return requestOptions\n }\n}\n\n/**\n * Creates a Gemini text adapter with explicit API key.\n * Type resolution happens here at the call site.\n */\nexport function createGeminiChat<TModel extends (typeof GEMINI_MODELS)[number]>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiTextConfig, 'apiKey'>,\n): GeminiTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>\n> {\n return new GeminiTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Gemini text adapter with automatic API key detection.\n * Type resolution happens here at the call site.\n */\nexport function geminiText<TModel extends (typeof GEMINI_MODELS)[number]>(\n model: TModel,\n config?: Omit<GeminiTextConfig, 'apiKey'>,\n): GeminiTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>\n> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiChat(model, apiKey, config)\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -16,4 +16,4 @@ export type { GeminiTTSVoice } from './model-meta.js';
|
|
|
16
16
|
export type { GeminiChatModelProviderOptionsByName, GeminiModelInputModalitiesByName, } from './model-meta.js';
|
|
17
17
|
export type { GeminiStructuredOutputOptions, GeminiThinkingOptions, } from './text/text-provider-options.js';
|
|
18
18
|
export type { GoogleGeminiTool } from './tools/index.js';
|
|
19
|
-
export type { GeminiTextMetadata, GeminiImageMetadata, GeminiAudioMetadata, GeminiVideoMetadata, GeminiDocumentMetadata,
|
|
19
|
+
export type { GeminiTextMetadata, GeminiImageMetadata, GeminiAudioMetadata, GeminiVideoMetadata, GeminiDocumentMetadata, GeminiMessageMetadataByModality, } from './message-types.js';
|
package/dist/esm/utils/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import { GoogleGenAI } from '@google/genai'\n\nexport interface GeminiClientConfig {\n apiKey: string\n}\n\n/**\n * Creates a Google Generative AI client instance\n */\nexport function createGeminiClient(config: GeminiClientConfig): GoogleGenAI {\n return new GoogleGenAI({\n apiKey: config.apiKey,\n })\n}\n\n/**\n * Gets Google API key from environment variables\n * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found\n */\nexport function getGeminiApiKeyFromEnv(): string {\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 the factory function with an explicit API key.',\n )\n }\n\n return key\n}\n\n/**\n * Generates a unique ID with a prefix\n */\nexport function generateId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import { GoogleGenAI } from '@google/genai'\nimport type { GoogleGenAIOptions } from '@google/genai'\n\nexport interface GeminiClientConfig extends GoogleGenAIOptions {\n apiKey: string\n}\n\n/**\n * Creates a Google Generative AI client instance\n */\nexport function createGeminiClient(config: GeminiClientConfig): GoogleGenAI {\n return new GoogleGenAI({\n ...config,\n apiKey: config.apiKey,\n })\n}\n\n/**\n * Gets Google API key from environment variables\n * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found\n */\nexport function getGeminiApiKeyFromEnv(): string {\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 the factory function with an explicit API key.',\n )\n }\n\n return key\n}\n\n/**\n * Generates a unique ID with a prefix\n */\nexport function generateId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":";AAUO,SAAS,mBAAmB,QAAyC;AAC1E,SAAO,IAAI,YAAY;AAAA,IACrB,GAAG;AAAA,IACH,QAAQ,OAAO;AAAA,EAAA,CAChB;AACH;AAMO,SAAS,yBAAiC;AAC/C,QAAM,MACJ,OAAO,eAAe,eAAgB,WAAmB,QAAQ,MAC5D,WAAmB,OAAO,MAC3B,OAAO,YAAY,cACjB,QAAQ,MACR;AACR,QAAM,MAAM,KAAK,kBAAkB,KAAK;AAExC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO;AACT;AAKO,SAAS,WAAW,QAAwB;AACjD,SAAO,GAAG,MAAM,IAAI,KAAK,KAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AAC3E;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-gemini",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Google Gemini adapter for TanStack AI",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"@google/genai": "^1.30.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@tanstack/ai": "^0.
|
|
36
|
+
"@tanstack/ai": "^0.4.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@vitest/coverage-v8": "4.0.14",
|
|
40
40
|
"vite": "^7.2.7",
|
|
41
|
-
"@tanstack/ai": "0.
|
|
41
|
+
"@tanstack/ai": "0.4.2"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "vite build",
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
generateId,
|
|
5
5
|
getGeminiApiKeyFromEnv,
|
|
6
6
|
} from '../utils'
|
|
7
|
-
|
|
8
7
|
import type { GoogleGenAI } from '@google/genai'
|
|
8
|
+
import type { GeminiClientConfig } from '../utils'
|
|
9
9
|
import type { SummarizeAdapter } from '@tanstack/ai/adapters'
|
|
10
10
|
import type {
|
|
11
11
|
StreamChunk,
|
|
@@ -13,6 +13,10 @@ import type {
|
|
|
13
13
|
SummarizationResult,
|
|
14
14
|
} from '@tanstack/ai'
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Configuration for Gemini summarize adapter
|
|
18
|
+
*/
|
|
19
|
+
export interface GeminiSummarizeConfig extends GeminiClientConfig {}
|
|
16
20
|
/**
|
|
17
21
|
* Available Gemini models for summarization
|
|
18
22
|
*/
|
|
@@ -66,15 +70,8 @@ export class GeminiSummarizeAdapter<
|
|
|
66
70
|
|
|
67
71
|
private client: GoogleGenAI
|
|
68
72
|
|
|
69
|
-
constructor(
|
|
70
|
-
|
|
71
|
-
model: TModel,
|
|
72
|
-
_options: GeminiSummarizeAdapterOptions = {},
|
|
73
|
-
) {
|
|
74
|
-
this.client =
|
|
75
|
-
typeof apiKeyOrClient === 'string'
|
|
76
|
-
? createGeminiClient({ apiKey: apiKeyOrClient })
|
|
77
|
-
: apiKeyOrClient
|
|
73
|
+
constructor(config: GeminiSummarizeConfig, model: TModel) {
|
|
74
|
+
this.client = createGeminiClient(config)
|
|
78
75
|
this.model = model
|
|
79
76
|
}
|
|
80
77
|
|
|
@@ -164,13 +161,12 @@ export class GeminiSummarizeAdapter<
|
|
|
164
161
|
if (part.text) {
|
|
165
162
|
accumulatedContent += part.text
|
|
166
163
|
yield {
|
|
167
|
-
type: '
|
|
168
|
-
id,
|
|
164
|
+
type: 'TEXT_MESSAGE_CONTENT',
|
|
165
|
+
messageId: id,
|
|
169
166
|
model,
|
|
170
167
|
timestamp: Date.now(),
|
|
171
168
|
delta: part.text,
|
|
172
169
|
content: accumulatedContent,
|
|
173
|
-
role: 'assistant',
|
|
174
170
|
}
|
|
175
171
|
}
|
|
176
172
|
}
|
|
@@ -184,8 +180,8 @@ export class GeminiSummarizeAdapter<
|
|
|
184
180
|
finishReason === FinishReason.SAFETY
|
|
185
181
|
) {
|
|
186
182
|
yield {
|
|
187
|
-
type: '
|
|
188
|
-
id,
|
|
183
|
+
type: 'RUN_FINISHED',
|
|
184
|
+
runId: id,
|
|
189
185
|
model,
|
|
190
186
|
timestamp: Date.now(),
|
|
191
187
|
finishReason:
|
|
@@ -225,9 +221,9 @@ export class GeminiSummarizeAdapter<
|
|
|
225
221
|
export function createGeminiSummarize<TModel extends GeminiSummarizeModel>(
|
|
226
222
|
apiKey: string,
|
|
227
223
|
model: TModel,
|
|
228
|
-
|
|
224
|
+
config?: Omit<GeminiSummarizeConfig, 'apiKey'>,
|
|
229
225
|
): GeminiSummarizeAdapter<TModel> {
|
|
230
|
-
return new GeminiSummarizeAdapter(apiKey, model
|
|
226
|
+
return new GeminiSummarizeAdapter({ ...config, apiKey }, model)
|
|
231
227
|
}
|
|
232
228
|
|
|
233
229
|
/**
|
|
@@ -235,8 +231,8 @@ export function createGeminiSummarize<TModel extends GeminiSummarizeModel>(
|
|
|
235
231
|
*/
|
|
236
232
|
export function geminiSummarize<TModel extends GeminiSummarizeModel>(
|
|
237
233
|
model: TModel,
|
|
238
|
-
|
|
234
|
+
config?: Omit<GeminiSummarizeConfig, 'apiKey'>,
|
|
239
235
|
): GeminiSummarizeAdapter<TModel> {
|
|
240
236
|
const apiKey = getGeminiApiKeyFromEnv()
|
|
241
|
-
return new GeminiSummarizeAdapter(apiKey, model
|
|
237
|
+
return new GeminiSummarizeAdapter({ ...config, apiKey }, model)
|
|
242
238
|
}
|
package/src/adapters/text.ts
CHANGED
|
@@ -30,13 +30,7 @@ import type {
|
|
|
30
30
|
TextOptions,
|
|
31
31
|
} from '@tanstack/ai'
|
|
32
32
|
import type { ExternalTextProviderOptions } from '../text/text-provider-options'
|
|
33
|
-
import type {
|
|
34
|
-
GeminiAudioMetadata,
|
|
35
|
-
GeminiDocumentMetadata,
|
|
36
|
-
GeminiImageMetadata,
|
|
37
|
-
GeminiMessageMetadataByModality,
|
|
38
|
-
GeminiVideoMetadata,
|
|
39
|
-
} from '../message-types'
|
|
33
|
+
import type { GeminiMessageMetadataByModality } from '../message-types'
|
|
40
34
|
import type { GeminiClientConfig } from '../utils'
|
|
41
35
|
|
|
42
36
|
/**
|
|
@@ -115,8 +109,7 @@ export class GeminiTextAdapter<
|
|
|
115
109
|
} catch (error) {
|
|
116
110
|
const timestamp = Date.now()
|
|
117
111
|
yield {
|
|
118
|
-
type: '
|
|
119
|
-
id: generateId(this.name),
|
|
112
|
+
type: 'RUN_ERROR',
|
|
120
113
|
model: options.model,
|
|
121
114
|
timestamp,
|
|
122
115
|
error: {
|
|
@@ -203,35 +196,78 @@ export class GeminiTextAdapter<
|
|
|
203
196
|
let accumulatedContent = ''
|
|
204
197
|
const toolCallMap = new Map<
|
|
205
198
|
string,
|
|
206
|
-
{ name: string; args: string; index: number }
|
|
199
|
+
{ name: string; args: string; index: number; started: boolean }
|
|
207
200
|
>()
|
|
208
201
|
let nextToolIndex = 0
|
|
209
202
|
|
|
203
|
+
// AG-UI lifecycle tracking
|
|
204
|
+
const runId = generateId(this.name)
|
|
205
|
+
const messageId = generateId(this.name)
|
|
206
|
+
let stepId: string | null = null
|
|
207
|
+
let hasEmittedRunStarted = false
|
|
208
|
+
let hasEmittedTextMessageStart = false
|
|
209
|
+
let hasEmittedStepStarted = false
|
|
210
|
+
|
|
210
211
|
for await (const chunk of result) {
|
|
212
|
+
// Emit RUN_STARTED on first chunk
|
|
213
|
+
if (!hasEmittedRunStarted) {
|
|
214
|
+
hasEmittedRunStarted = true
|
|
215
|
+
yield {
|
|
216
|
+
type: 'RUN_STARTED',
|
|
217
|
+
runId,
|
|
218
|
+
model,
|
|
219
|
+
timestamp,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
211
223
|
if (chunk.candidates?.[0]?.content?.parts) {
|
|
212
224
|
const parts = chunk.candidates[0].content.parts
|
|
213
225
|
|
|
214
226
|
for (const part of parts) {
|
|
215
227
|
if (part.text) {
|
|
216
228
|
if (part.thought) {
|
|
229
|
+
// Emit STEP_STARTED on first thinking content
|
|
230
|
+
if (!hasEmittedStepStarted) {
|
|
231
|
+
hasEmittedStepStarted = true
|
|
232
|
+
stepId = generateId(this.name)
|
|
233
|
+
yield {
|
|
234
|
+
type: 'STEP_STARTED',
|
|
235
|
+
stepId,
|
|
236
|
+
model,
|
|
237
|
+
timestamp,
|
|
238
|
+
stepType: 'thinking',
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
217
242
|
yield {
|
|
218
|
-
type: '
|
|
219
|
-
|
|
220
|
-
delta: part.text,
|
|
221
|
-
id: generateId(this.name),
|
|
243
|
+
type: 'STEP_FINISHED',
|
|
244
|
+
stepId: stepId || generateId(this.name),
|
|
222
245
|
model,
|
|
223
246
|
timestamp,
|
|
247
|
+
delta: part.text,
|
|
248
|
+
content: part.text,
|
|
224
249
|
}
|
|
225
250
|
} else {
|
|
251
|
+
// Emit TEXT_MESSAGE_START on first text content
|
|
252
|
+
if (!hasEmittedTextMessageStart) {
|
|
253
|
+
hasEmittedTextMessageStart = true
|
|
254
|
+
yield {
|
|
255
|
+
type: 'TEXT_MESSAGE_START',
|
|
256
|
+
messageId,
|
|
257
|
+
model,
|
|
258
|
+
timestamp,
|
|
259
|
+
role: 'assistant',
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
226
263
|
accumulatedContent += part.text
|
|
227
264
|
yield {
|
|
228
|
-
type: '
|
|
229
|
-
|
|
265
|
+
type: 'TEXT_MESSAGE_CONTENT',
|
|
266
|
+
messageId,
|
|
230
267
|
model,
|
|
231
268
|
timestamp,
|
|
232
269
|
delta: part.text,
|
|
233
270
|
content: accumulatedContent,
|
|
234
|
-
role: 'assistant',
|
|
235
271
|
}
|
|
236
272
|
}
|
|
237
273
|
}
|
|
@@ -252,6 +288,7 @@ export class GeminiTextAdapter<
|
|
|
252
288
|
? functionArgs
|
|
253
289
|
: JSON.stringify(functionArgs),
|
|
254
290
|
index: nextToolIndex++,
|
|
291
|
+
started: false,
|
|
255
292
|
}
|
|
256
293
|
toolCallMap.set(toolCallId, toolCallData)
|
|
257
294
|
} else {
|
|
@@ -271,33 +308,51 @@ export class GeminiTextAdapter<
|
|
|
271
308
|
}
|
|
272
309
|
}
|
|
273
310
|
|
|
311
|
+
// Emit TOOL_CALL_START if not already started
|
|
312
|
+
if (!toolCallData.started) {
|
|
313
|
+
toolCallData.started = true
|
|
314
|
+
yield {
|
|
315
|
+
type: 'TOOL_CALL_START',
|
|
316
|
+
toolCallId,
|
|
317
|
+
toolName: toolCallData.name,
|
|
318
|
+
model,
|
|
319
|
+
timestamp,
|
|
320
|
+
index: toolCallData.index,
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Emit TOOL_CALL_ARGS
|
|
274
325
|
yield {
|
|
275
|
-
type: '
|
|
276
|
-
|
|
326
|
+
type: 'TOOL_CALL_ARGS',
|
|
327
|
+
toolCallId,
|
|
277
328
|
model,
|
|
278
329
|
timestamp,
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
type: 'function',
|
|
282
|
-
function: {
|
|
283
|
-
name: toolCallData.name,
|
|
284
|
-
arguments: toolCallData.args,
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
index: toolCallData.index,
|
|
330
|
+
delta: toolCallData.args,
|
|
331
|
+
args: toolCallData.args,
|
|
288
332
|
}
|
|
289
333
|
}
|
|
290
334
|
}
|
|
291
335
|
} else if (chunk.data) {
|
|
336
|
+
// Emit TEXT_MESSAGE_START on first text content
|
|
337
|
+
if (!hasEmittedTextMessageStart) {
|
|
338
|
+
hasEmittedTextMessageStart = true
|
|
339
|
+
yield {
|
|
340
|
+
type: 'TEXT_MESSAGE_START',
|
|
341
|
+
messageId,
|
|
342
|
+
model,
|
|
343
|
+
timestamp,
|
|
344
|
+
role: 'assistant',
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
292
348
|
accumulatedContent += chunk.data
|
|
293
349
|
yield {
|
|
294
|
-
type: '
|
|
295
|
-
|
|
350
|
+
type: 'TEXT_MESSAGE_CONTENT',
|
|
351
|
+
messageId,
|
|
296
352
|
model,
|
|
297
353
|
timestamp,
|
|
298
354
|
delta: chunk.data,
|
|
299
355
|
content: accumulatedContent,
|
|
300
|
-
role: 'assistant',
|
|
301
356
|
}
|
|
302
357
|
}
|
|
303
358
|
|
|
@@ -314,53 +369,98 @@ export class GeminiTextAdapter<
|
|
|
314
369
|
`${functionCall.name}_${Date.now()}_${nextToolIndex}`
|
|
315
370
|
const functionArgs = functionCall.args || {}
|
|
316
371
|
|
|
372
|
+
const argsString =
|
|
373
|
+
typeof functionArgs === 'string'
|
|
374
|
+
? functionArgs
|
|
375
|
+
: JSON.stringify(functionArgs)
|
|
376
|
+
|
|
317
377
|
toolCallMap.set(toolCallId, {
|
|
318
378
|
name: functionCall.name || '',
|
|
319
|
-
args:
|
|
320
|
-
typeof functionArgs === 'string'
|
|
321
|
-
? functionArgs
|
|
322
|
-
: JSON.stringify(functionArgs),
|
|
379
|
+
args: argsString,
|
|
323
380
|
index: nextToolIndex++,
|
|
381
|
+
started: true,
|
|
324
382
|
})
|
|
325
383
|
|
|
384
|
+
// Emit TOOL_CALL_START
|
|
326
385
|
yield {
|
|
327
|
-
type: '
|
|
328
|
-
|
|
386
|
+
type: 'TOOL_CALL_START',
|
|
387
|
+
toolCallId,
|
|
388
|
+
toolName: functionCall.name || '',
|
|
329
389
|
model,
|
|
330
390
|
timestamp,
|
|
331
|
-
toolCall: {
|
|
332
|
-
id: toolCallId,
|
|
333
|
-
type: 'function',
|
|
334
|
-
function: {
|
|
335
|
-
name: functionCall.name || '',
|
|
336
|
-
arguments:
|
|
337
|
-
typeof functionArgs === 'string'
|
|
338
|
-
? functionArgs
|
|
339
|
-
: JSON.stringify(functionArgs),
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
391
|
index: nextToolIndex - 1,
|
|
343
392
|
}
|
|
393
|
+
|
|
394
|
+
// Emit TOOL_CALL_END with parsed input
|
|
395
|
+
let parsedInput: unknown = {}
|
|
396
|
+
try {
|
|
397
|
+
parsedInput =
|
|
398
|
+
typeof functionArgs === 'string'
|
|
399
|
+
? JSON.parse(functionArgs)
|
|
400
|
+
: functionArgs
|
|
401
|
+
} catch {
|
|
402
|
+
parsedInput = {}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
yield {
|
|
406
|
+
type: 'TOOL_CALL_END',
|
|
407
|
+
toolCallId,
|
|
408
|
+
toolName: functionCall.name || '',
|
|
409
|
+
model,
|
|
410
|
+
timestamp,
|
|
411
|
+
input: parsedInput,
|
|
412
|
+
}
|
|
344
413
|
}
|
|
345
414
|
}
|
|
346
415
|
}
|
|
347
416
|
}
|
|
417
|
+
|
|
418
|
+
// Emit TOOL_CALL_END for all tracked tool calls
|
|
419
|
+
for (const [toolCallId, toolCallData] of toolCallMap.entries()) {
|
|
420
|
+
let parsedInput: unknown = {}
|
|
421
|
+
try {
|
|
422
|
+
parsedInput = JSON.parse(toolCallData.args)
|
|
423
|
+
} catch {
|
|
424
|
+
parsedInput = {}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
yield {
|
|
428
|
+
type: 'TOOL_CALL_END',
|
|
429
|
+
toolCallId,
|
|
430
|
+
toolName: toolCallData.name,
|
|
431
|
+
model,
|
|
432
|
+
timestamp,
|
|
433
|
+
input: parsedInput,
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
348
437
|
if (finishReason === FinishReason.MAX_TOKENS) {
|
|
349
438
|
yield {
|
|
350
|
-
type: '
|
|
351
|
-
|
|
439
|
+
type: 'RUN_ERROR',
|
|
440
|
+
runId,
|
|
352
441
|
model,
|
|
353
442
|
timestamp,
|
|
354
443
|
error: {
|
|
355
444
|
message:
|
|
356
445
|
'The response was cut off because the maximum token limit was reached.',
|
|
446
|
+
code: 'max_tokens',
|
|
357
447
|
},
|
|
358
448
|
}
|
|
359
449
|
}
|
|
360
450
|
|
|
451
|
+
// Emit TEXT_MESSAGE_END if we had text content
|
|
452
|
+
if (hasEmittedTextMessageStart) {
|
|
453
|
+
yield {
|
|
454
|
+
type: 'TEXT_MESSAGE_END',
|
|
455
|
+
messageId,
|
|
456
|
+
model,
|
|
457
|
+
timestamp,
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
361
461
|
yield {
|
|
362
|
-
type: '
|
|
363
|
-
|
|
462
|
+
type: 'RUN_FINISHED',
|
|
463
|
+
runId,
|
|
364
464
|
model,
|
|
365
465
|
timestamp,
|
|
366
466
|
finishReason: toolCallMap.size > 0 ? 'tool_calls' : 'stop',
|
|
@@ -377,20 +477,6 @@ export class GeminiTextAdapter<
|
|
|
377
477
|
}
|
|
378
478
|
|
|
379
479
|
private convertContentPartToGemini(part: ContentPart): Part {
|
|
380
|
-
const getDefaultFileType = (
|
|
381
|
-
part: 'image' | 'audio' | 'video' | 'document',
|
|
382
|
-
) => {
|
|
383
|
-
switch (part) {
|
|
384
|
-
case 'image':
|
|
385
|
-
return 'image/jpeg'
|
|
386
|
-
case 'audio':
|
|
387
|
-
return 'audio/mp3'
|
|
388
|
-
case 'video':
|
|
389
|
-
return 'video/mp4'
|
|
390
|
-
case 'document':
|
|
391
|
-
return 'application/pdf'
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
480
|
switch (part.type) {
|
|
395
481
|
case 'text':
|
|
396
482
|
return { text: part.content }
|
|
@@ -398,24 +484,26 @@ export class GeminiTextAdapter<
|
|
|
398
484
|
case 'audio':
|
|
399
485
|
case 'video':
|
|
400
486
|
case 'document': {
|
|
401
|
-
const metadata = part.metadata as
|
|
402
|
-
| GeminiDocumentMetadata
|
|
403
|
-
| GeminiImageMetadata
|
|
404
|
-
| GeminiVideoMetadata
|
|
405
|
-
| GeminiAudioMetadata
|
|
406
|
-
| undefined
|
|
407
487
|
if (part.source.type === 'data') {
|
|
408
488
|
return {
|
|
409
489
|
inlineData: {
|
|
410
490
|
data: part.source.value,
|
|
411
|
-
mimeType:
|
|
491
|
+
mimeType: part.source.mimeType,
|
|
412
492
|
},
|
|
413
493
|
}
|
|
414
494
|
} else {
|
|
495
|
+
// For URL sources, use provided mimeType or fall back to reasonable defaults
|
|
496
|
+
const defaultMimeType = {
|
|
497
|
+
image: 'image/jpeg',
|
|
498
|
+
audio: 'audio/mp3',
|
|
499
|
+
video: 'video/mp4',
|
|
500
|
+
document: 'application/pdf',
|
|
501
|
+
}[part.type]
|
|
502
|
+
|
|
415
503
|
return {
|
|
416
504
|
fileData: {
|
|
417
505
|
fileUri: part.source.value,
|
|
418
|
-
mimeType:
|
|
506
|
+
mimeType: part.source.mimeType ?? defaultMimeType,
|
|
419
507
|
},
|
|
420
508
|
}
|
|
421
509
|
}
|
package/src/index.ts
CHANGED
package/src/utils/client.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GoogleGenAI } from '@google/genai'
|
|
2
|
+
import type { GoogleGenAIOptions } from '@google/genai'
|
|
2
3
|
|
|
3
|
-
export interface GeminiClientConfig {
|
|
4
|
+
export interface GeminiClientConfig extends GoogleGenAIOptions {
|
|
4
5
|
apiKey: string
|
|
5
6
|
}
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ export interface GeminiClientConfig {
|
|
|
9
10
|
*/
|
|
10
11
|
export function createGeminiClient(config: GeminiClientConfig): GoogleGenAI {
|
|
11
12
|
return new GoogleGenAI({
|
|
13
|
+
...config,
|
|
12
14
|
apiKey: config.apiKey,
|
|
13
15
|
})
|
|
14
16
|
}
|