@tanstack/ai-gemini 0.8.9 → 0.9.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/dist/esm/adapters/image.js +28 -13
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/summarize.js +98 -70
- package/dist/esm/adapters/summarize.js.map +1 -1
- package/dist/esm/adapters/text.d.ts +9 -4
- package/dist/esm/adapters/text.js +21 -2
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/tts.js +51 -38
- package/dist/esm/adapters/tts.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/model-meta.d.ts +38 -10
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/tools/code-execution-tool.d.ts +6 -3
- package/dist/esm/tools/code-execution-tool.js +8 -0
- package/dist/esm/tools/code-execution-tool.js.map +1 -1
- package/dist/esm/tools/computer-use-tool.d.ts +6 -3
- package/dist/esm/tools/computer-use-tool.js +11 -0
- package/dist/esm/tools/computer-use-tool.js.map +1 -1
- package/dist/esm/tools/file-search-tool.d.ts +6 -3
- package/dist/esm/tools/file-search-tool.js +9 -1
- package/dist/esm/tools/file-search-tool.js.map +1 -1
- package/dist/esm/tools/function-declaration-tool.js +29 -0
- package/dist/esm/tools/function-declaration-tool.js.map +1 -0
- package/dist/esm/tools/google-maps-tool.d.ts +6 -3
- package/dist/esm/tools/google-maps-tool.js +9 -1
- package/dist/esm/tools/google-maps-tool.js.map +1 -1
- package/dist/esm/tools/google-search-retriveal-tool.d.ts +6 -3
- package/dist/esm/tools/google-search-retriveal-tool.js +9 -1
- package/dist/esm/tools/google-search-retriveal-tool.js.map +1 -1
- package/dist/esm/tools/google-search-tool.d.ts +6 -3
- package/dist/esm/tools/google-search-tool.js +9 -1
- package/dist/esm/tools/google-search-tool.js.map +1 -1
- package/dist/esm/tools/index.d.ts +9 -0
- package/dist/esm/tools/index.js +21 -0
- package/dist/esm/tools/index.js.map +1 -0
- package/dist/esm/tools/url-context-tool.d.ts +6 -3
- package/dist/esm/tools/url-context-tool.js +9 -1
- package/dist/esm/tools/url-context-tool.js.map +1 -1
- package/package.json +8 -3
- package/src/adapters/image.ts +31 -15
- package/src/adapters/summarize.ts +111 -80
- package/src/adapters/text.ts +41 -5
- package/src/adapters/tts.ts +62 -48
- package/src/index.ts +1 -0
- package/src/model-meta.ts +59 -72
- package/src/tools/code-execution-tool.ts +10 -4
- package/src/tools/computer-use-tool.ts +13 -5
- package/src/tools/file-search-tool.ts +13 -5
- package/src/tools/google-maps-tool.ts +13 -5
- package/src/tools/google-search-retriveal-tool.ts +15 -6
- package/src/tools/google-search-tool.ts +13 -5
- package/src/tools/index.ts +51 -0
- package/src/tools/url-context-tool.ts +10 -4
|
@@ -9,20 +9,35 @@ class GeminiImageAdapter extends BaseImageAdapter {
|
|
|
9
9
|
this.client = createGeminiClient(config);
|
|
10
10
|
}
|
|
11
11
|
async generateImages(options) {
|
|
12
|
-
const { model, prompt } = options;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const { model, prompt, logger } = options;
|
|
13
|
+
logger.request(
|
|
14
|
+
`activity=generateImage provider=gemini model=${this.model}`,
|
|
15
|
+
{
|
|
16
|
+
provider: "gemini",
|
|
17
|
+
model: this.model
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
try {
|
|
21
|
+
validatePrompt({ prompt, model });
|
|
22
|
+
if (this.isGeminiImageModel(model)) {
|
|
23
|
+
return await this.generateWithGeminiApi(options);
|
|
24
|
+
}
|
|
25
|
+
validateImageSize(model, options.size);
|
|
26
|
+
validateNumberOfImages(model, options.numberOfImages);
|
|
27
|
+
const config = this.buildImagenConfig(options);
|
|
28
|
+
const response = await this.client.models.generateImages({
|
|
29
|
+
model,
|
|
30
|
+
prompt,
|
|
31
|
+
config
|
|
32
|
+
});
|
|
33
|
+
return this.transformImagenResponse(model, response);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
logger.errors("gemini.generateImage fatal", {
|
|
36
|
+
error,
|
|
37
|
+
source: "gemini.generateImage"
|
|
38
|
+
});
|
|
39
|
+
throw error;
|
|
16
40
|
}
|
|
17
|
-
validateImageSize(model, options.size);
|
|
18
|
-
validateNumberOfImages(model, options.numberOfImages);
|
|
19
|
-
const config = this.buildImagenConfig(options);
|
|
20
|
-
const response = await this.client.models.generateImages({
|
|
21
|
-
model,
|
|
22
|
-
prompt,
|
|
23
|
-
config
|
|
24
|
-
});
|
|
25
|
-
return this.transformImagenResponse(model, response);
|
|
26
41
|
}
|
|
27
42
|
isGeminiImageModel(model) {
|
|
28
43
|
return model.startsWith("gemini-");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.js","sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport {\n parseNativeImageSize,\n sizeToAspectRatio,\n validateImageSize,\n validateNumberOfImages,\n validatePrompt,\n} from '../image/image-provider-options'\nimport type { GEMINI_IMAGE_MODELS } from '../model-meta'\nimport type {\n GeminiImageModelProviderOptionsByName,\n GeminiImageModelSizeByName,\n GeminiImageProviderOptions,\n} from '../image/image-provider-options'\nimport type {\n GeneratedImage,\n ImageGenerationOptions,\n ImageGenerationResult,\n} from '@tanstack/ai'\nimport type {\n GenerateContentConfig,\n GenerateContentResponse,\n GenerateImagesConfig,\n GenerateImagesResponse,\n GoogleGenAI,\n} from '@google/genai'\nimport type { GeminiClientConfig } from '../utils'\n\n/**\n * Configuration for Gemini image adapter\n */\nexport interface GeminiImageConfig extends GeminiClientConfig {}\n\n/** Model type for Gemini Image */\nexport type GeminiImageModel = (typeof GEMINI_IMAGE_MODELS)[number]\n\n/**\n * Gemini Image Generation Adapter\n *\n * Tree-shakeable adapter for Gemini image generation functionality.\n * Supports Imagen 3/4 models (via generateImages API) and Gemini native\n * image models like Nano Banana 2 (via generateContent API).\n *\n * Features:\n * - Aspect ratio-based image sizing\n * - Person generation controls\n * - Safety filtering\n * - Watermark options\n * - Extended resolution tiers (Nano Banana 2)\n */\nexport class GeminiImageAdapter<\n TModel extends GeminiImageModel,\n> extends BaseImageAdapter<\n TModel,\n GeminiImageProviderOptions,\n GeminiImageModelProviderOptionsByName,\n GeminiImageModelSizeByName\n> {\n readonly kind = 'image' as const\n readonly name = 'gemini' as const\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: GeminiImageProviderOptions\n modelProviderOptionsByName: GeminiImageModelProviderOptionsByName\n modelSizeByName: GeminiImageModelSizeByName\n }\n\n private client: GoogleGenAI\n\n constructor(config: GeminiImageConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n }\n\n async generateImages(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): Promise<ImageGenerationResult> {\n const { model, prompt } = options\n\n validatePrompt({ prompt, model })\n\n if (this.isGeminiImageModel(model)) {\n return this.generateWithGeminiApi(options)\n }\n\n // Imagen models path (generateImages API)\n validateImageSize(model, options.size)\n validateNumberOfImages(model, options.numberOfImages)\n\n const config = this.buildImagenConfig(options)\n\n const response = await this.client.models.generateImages({\n model,\n prompt,\n config,\n })\n\n return this.transformImagenResponse(model, response)\n }\n\n private isGeminiImageModel(model: string): boolean {\n return model.startsWith('gemini-')\n }\n\n private async generateWithGeminiApi(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): Promise<ImageGenerationResult> {\n const { model, prompt, size, numberOfImages, modelOptions } = options\n\n const parsedSize = size ? parseNativeImageSize(size) : undefined\n\n // The generateContent API has no numberOfImages parameter.\n // Instead, augment the prompt to request multiple images when needed.\n const augmentedPrompt =\n numberOfImages && numberOfImages > 1\n ? `${prompt} Generate ${numberOfImages} distinct images.`\n : prompt\n\n const config: GenerateContentConfig = {\n // Include TEXT so the model can interleave descriptions between images\n responseModalities: ['TEXT', 'IMAGE'],\n ...(parsedSize && {\n imageConfig: {\n ...(parsedSize.aspectRatio && {\n aspectRatio: parsedSize.aspectRatio,\n }),\n ...(parsedSize.resolution && {\n imageSize: parsedSize.resolution,\n }),\n },\n }),\n ...modelOptions,\n }\n\n const response = await this.client.models.generateContent({\n model,\n contents: augmentedPrompt,\n config,\n })\n\n return this.transformGeminiResponse(model, response)\n }\n\n private transformGeminiResponse(\n model: string,\n response: GenerateContentResponse,\n ): ImageGenerationResult {\n const images: Array<GeneratedImage> = []\n const parts = response.candidates?.[0]?.content?.parts ?? []\n\n for (const part of parts) {\n if (\n part.inlineData?.data &&\n typeof part.inlineData.data === 'string' &&\n part.inlineData.data.length > 0\n ) {\n images.push({ b64Json: part.inlineData.data })\n }\n }\n\n return {\n id: generateId(this.name),\n model,\n images,\n usage: undefined,\n }\n }\n\n private buildImagenConfig(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): GenerateImagesConfig {\n const { size, numberOfImages, modelOptions } = options\n\n return {\n numberOfImages: numberOfImages ?? 1,\n // Map size to aspect ratio if provided (modelOptions.aspectRatio will override)\n aspectRatio: size ? sizeToAspectRatio(size) : undefined,\n ...modelOptions,\n }\n }\n\n private transformImagenResponse(\n model: string,\n response: GenerateImagesResponse,\n ): ImageGenerationResult {\n const images: Array<GeneratedImage> = (response.generatedImages ?? []).map(\n (item) => ({\n b64Json: item.image?.imageBytes,\n revisedPrompt: item.enhancedPrompt,\n }),\n )\n\n return {\n id: generateId(this.name),\n model,\n images,\n usage: undefined,\n }\n }\n}\n\n/**\n * Creates a Gemini image adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'imagen-3.0-generate-002')\n * @param apiKey - Your Google API key\n * @param config - Optional additional configuration\n * @returns Configured Gemini image adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGeminiImage('imagen-3.0-generate-002', \"your-api-key\");\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A cute baby sea otter'\n * });\n * ```\n */\nexport function createGeminiImage<TModel extends GeminiImageModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<TModel> {\n return new GeminiImageAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Gemini image adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'imagen-4.0-generate-001')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Gemini image adapter instance with resolved types\n * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses GOOGLE_API_KEY from environment\n * const adapter = geminiImage('imagen-4.0-generate-001');\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A beautiful sunset over mountains'\n * });\n * ```\n */\nexport function geminiImage<TModel extends GeminiImageModel>(\n model: TModel,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<TModel> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiImage(model, apiKey, config)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"image.js","sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport {\n parseNativeImageSize,\n sizeToAspectRatio,\n validateImageSize,\n validateNumberOfImages,\n validatePrompt,\n} from '../image/image-provider-options'\nimport type { GEMINI_IMAGE_MODELS } from '../model-meta'\nimport type {\n GeminiImageModelProviderOptionsByName,\n GeminiImageModelSizeByName,\n GeminiImageProviderOptions,\n} from '../image/image-provider-options'\nimport type {\n GeneratedImage,\n ImageGenerationOptions,\n ImageGenerationResult,\n} from '@tanstack/ai'\nimport type {\n GenerateContentConfig,\n GenerateContentResponse,\n GenerateImagesConfig,\n GenerateImagesResponse,\n GoogleGenAI,\n} from '@google/genai'\nimport type { GeminiClientConfig } from '../utils'\n\n/**\n * Configuration for Gemini image adapter\n */\nexport interface GeminiImageConfig extends GeminiClientConfig {}\n\n/** Model type for Gemini Image */\nexport type GeminiImageModel = (typeof GEMINI_IMAGE_MODELS)[number]\n\n/**\n * Gemini Image Generation Adapter\n *\n * Tree-shakeable adapter for Gemini image generation functionality.\n * Supports Imagen 3/4 models (via generateImages API) and Gemini native\n * image models like Nano Banana 2 (via generateContent API).\n *\n * Features:\n * - Aspect ratio-based image sizing\n * - Person generation controls\n * - Safety filtering\n * - Watermark options\n * - Extended resolution tiers (Nano Banana 2)\n */\nexport class GeminiImageAdapter<\n TModel extends GeminiImageModel,\n> extends BaseImageAdapter<\n TModel,\n GeminiImageProviderOptions,\n GeminiImageModelProviderOptionsByName,\n GeminiImageModelSizeByName\n> {\n readonly kind = 'image' as const\n readonly name = 'gemini' as const\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: GeminiImageProviderOptions\n modelProviderOptionsByName: GeminiImageModelProviderOptionsByName\n modelSizeByName: GeminiImageModelSizeByName\n }\n\n private client: GoogleGenAI\n\n constructor(config: GeminiImageConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n }\n\n async generateImages(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): Promise<ImageGenerationResult> {\n const { model, prompt, logger } = options\n\n logger.request(\n `activity=generateImage provider=gemini model=${this.model}`,\n {\n provider: 'gemini',\n model: this.model,\n },\n )\n\n try {\n validatePrompt({ prompt, model })\n\n if (this.isGeminiImageModel(model)) {\n return await this.generateWithGeminiApi(options)\n }\n\n // Imagen models path (generateImages API)\n validateImageSize(model, options.size)\n validateNumberOfImages(model, options.numberOfImages)\n\n const config = this.buildImagenConfig(options)\n\n const response = await this.client.models.generateImages({\n model,\n prompt,\n config,\n })\n\n return this.transformImagenResponse(model, response)\n } catch (error) {\n logger.errors('gemini.generateImage fatal', {\n error,\n source: 'gemini.generateImage',\n })\n throw error\n }\n }\n\n private isGeminiImageModel(model: string): boolean {\n return model.startsWith('gemini-')\n }\n\n private async generateWithGeminiApi(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): Promise<ImageGenerationResult> {\n const { model, prompt, size, numberOfImages, modelOptions } = options\n\n const parsedSize = size ? parseNativeImageSize(size) : undefined\n\n // The generateContent API has no numberOfImages parameter.\n // Instead, augment the prompt to request multiple images when needed.\n const augmentedPrompt =\n numberOfImages && numberOfImages > 1\n ? `${prompt} Generate ${numberOfImages} distinct images.`\n : prompt\n\n const config: GenerateContentConfig = {\n // Include TEXT so the model can interleave descriptions between images\n responseModalities: ['TEXT', 'IMAGE'],\n ...(parsedSize && {\n imageConfig: {\n ...(parsedSize.aspectRatio && {\n aspectRatio: parsedSize.aspectRatio,\n }),\n ...(parsedSize.resolution && {\n imageSize: parsedSize.resolution,\n }),\n },\n }),\n ...modelOptions,\n }\n\n const response = await this.client.models.generateContent({\n model,\n contents: augmentedPrompt,\n config,\n })\n\n return this.transformGeminiResponse(model, response)\n }\n\n private transformGeminiResponse(\n model: string,\n response: GenerateContentResponse,\n ): ImageGenerationResult {\n const images: Array<GeneratedImage> = []\n const parts = response.candidates?.[0]?.content?.parts ?? []\n\n for (const part of parts) {\n if (\n part.inlineData?.data &&\n typeof part.inlineData.data === 'string' &&\n part.inlineData.data.length > 0\n ) {\n images.push({ b64Json: part.inlineData.data })\n }\n }\n\n return {\n id: generateId(this.name),\n model,\n images,\n usage: undefined,\n }\n }\n\n private buildImagenConfig(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): GenerateImagesConfig {\n const { size, numberOfImages, modelOptions } = options\n\n return {\n numberOfImages: numberOfImages ?? 1,\n // Map size to aspect ratio if provided (modelOptions.aspectRatio will override)\n aspectRatio: size ? sizeToAspectRatio(size) : undefined,\n ...modelOptions,\n }\n }\n\n private transformImagenResponse(\n model: string,\n response: GenerateImagesResponse,\n ): ImageGenerationResult {\n const images: Array<GeneratedImage> = (response.generatedImages ?? []).map(\n (item) => ({\n b64Json: item.image?.imageBytes,\n revisedPrompt: item.enhancedPrompt,\n }),\n )\n\n return {\n id: generateId(this.name),\n model,\n images,\n usage: undefined,\n }\n }\n}\n\n/**\n * Creates a Gemini image adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'imagen-3.0-generate-002')\n * @param apiKey - Your Google API key\n * @param config - Optional additional configuration\n * @returns Configured Gemini image adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGeminiImage('imagen-3.0-generate-002', \"your-api-key\");\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A cute baby sea otter'\n * });\n * ```\n */\nexport function createGeminiImage<TModel extends GeminiImageModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<TModel> {\n return new GeminiImageAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Gemini image adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'imagen-4.0-generate-001')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Gemini image adapter instance with resolved types\n * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses GOOGLE_API_KEY from environment\n * const adapter = geminiImage('imagen-4.0-generate-001');\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A beautiful sunset over mountains'\n * });\n * ```\n */\nexport function geminiImage<TModel extends GeminiImageModel>(\n model: TModel,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<TModel> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiImage(model, apiKey, config)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -16,41 +16,55 @@ class GeminiSummarizeAdapter {
|
|
|
16
16
|
this.model = model;
|
|
17
17
|
}
|
|
18
18
|
async summarize(options) {
|
|
19
|
+
const { logger } = options;
|
|
19
20
|
const model = options.model;
|
|
21
|
+
logger.request(`activity=summarize provider=gemini`, {
|
|
22
|
+
provider: "gemini",
|
|
23
|
+
model
|
|
24
|
+
});
|
|
20
25
|
const formatInstructions = this.getFormatInstructions(options.style);
|
|
21
26
|
const lengthInstructions = options.maxLength ? ` Keep the summary under ${options.maxLength} tokens.` : "";
|
|
22
27
|
const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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:
|
|
30
36
|
|
|
31
37
|
${options.text}` }
|
|
32
|
-
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
config: {
|
|
42
|
+
systemInstruction: systemPrompt
|
|
33
43
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
+
}
|
|
52
65
|
}
|
|
53
66
|
async *summarizeStream(options) {
|
|
67
|
+
const { logger } = options;
|
|
54
68
|
const model = options.model;
|
|
55
69
|
const id = generateId("sum");
|
|
56
70
|
let accumulatedContent = "";
|
|
@@ -59,57 +73,71 @@ ${options.text}` }
|
|
|
59
73
|
const formatInstructions = this.getFormatInstructions(options.style);
|
|
60
74
|
const lengthInstructions = options.maxLength ? ` Keep the summary under ${options.maxLength} words.` : "";
|
|
61
75
|
const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`;
|
|
62
|
-
|
|
76
|
+
logger.request(`activity=summarize provider=gemini`, {
|
|
77
|
+
provider: "gemini",
|
|
63
78
|
model,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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:
|
|
69
89
|
|
|
70
90
|
${options.text}` }
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
],
|
|
74
|
-
config: {
|
|
75
|
-
systemInstruction: systemPrompt
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
for await (const chunk of result) {
|
|
79
|
-
if (chunk.usageMetadata) {
|
|
80
|
-
inputTokens = chunk.usageMetadata.promptTokenCount ?? inputTokens;
|
|
81
|
-
outputTokens = chunk.usageMetadata.candidatesTokenCount ?? outputTokens;
|
|
82
|
-
}
|
|
83
|
-
if (chunk.candidates?.[0]?.content?.parts) {
|
|
84
|
-
for (const part of chunk.candidates[0].content.parts) {
|
|
85
|
-
if (part.text) {
|
|
86
|
-
accumulatedContent += part.text;
|
|
87
|
-
yield asChunk({
|
|
88
|
-
type: "TEXT_MESSAGE_CONTENT",
|
|
89
|
-
messageId: id,
|
|
90
|
-
model,
|
|
91
|
-
timestamp: Date.now(),
|
|
92
|
-
delta: part.text,
|
|
93
|
-
content: accumulatedContent
|
|
94
|
-
});
|
|
91
|
+
]
|
|
95
92
|
}
|
|
93
|
+
],
|
|
94
|
+
config: {
|
|
95
|
+
systemInstruction: systemPrompt
|
|
96
96
|
}
|
|
97
|
-
}
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
+
}
|
|
110
117
|
}
|
|
111
|
-
}
|
|
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
|
+
}
|
|
112
134
|
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.errors("gemini.summarize fatal", {
|
|
137
|
+
error,
|
|
138
|
+
source: "gemini.summarize"
|
|
139
|
+
});
|
|
140
|
+
throw error;
|
|
113
141
|
}
|
|
114
142
|
}
|
|
115
143
|
getFormatInstructions(style) {
|
|
@@ -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 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 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 }\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 { 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,5 +1,5 @@
|
|
|
1
1
|
import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from '@tanstack/ai/adapters';
|
|
2
|
-
import { GEMINI_MODELS, GeminiChatModelProviderOptionsByName, GeminiModelInputModalitiesByName } from '../model-meta.js';
|
|
2
|
+
import { GEMINI_MODELS, GeminiChatModelProviderOptionsByName, GeminiChatModelToolCapabilitiesByName, GeminiModelInputModalitiesByName } from '../model-meta.js';
|
|
3
3
|
import { Modality, StreamChunk, TextOptions } from '@tanstack/ai';
|
|
4
4
|
import { ExternalTextProviderOptions } from '../text/text-provider-options.js';
|
|
5
5
|
import { GeminiMessageMetadataByModality } from '../message-types.js';
|
|
@@ -23,13 +23,18 @@ type ResolveProviderOptions<TModel extends string> = TModel extends keyof Gemini
|
|
|
23
23
|
* If the model has explicit modalities in the map, use those; otherwise use all modalities.
|
|
24
24
|
*/
|
|
25
25
|
type ResolveInputModalities<TModel extends string> = TModel extends keyof GeminiModelInputModalitiesByName ? GeminiModelInputModalitiesByName[TModel] : readonly ['text', 'image', 'audio', 'video', 'document'];
|
|
26
|
+
/**
|
|
27
|
+
* Resolve tool capabilities for a specific model.
|
|
28
|
+
* If the model has explicit tools in the map, use those; otherwise use empty tuple.
|
|
29
|
+
*/
|
|
30
|
+
type ResolveToolCapabilities<TModel extends string> = TModel extends keyof GeminiChatModelToolCapabilitiesByName ? NonNullable<GeminiChatModelToolCapabilitiesByName[TModel]> : readonly [];
|
|
26
31
|
/**
|
|
27
32
|
* Gemini Text (Chat) Adapter
|
|
28
33
|
*
|
|
29
34
|
* Tree-shakeable adapter for Gemini chat/text completion functionality.
|
|
30
35
|
* Import only what you need for smaller bundle sizes.
|
|
31
36
|
*/
|
|
32
|
-
export declare class GeminiTextAdapter<TModel extends (typeof GEMINI_MODELS)[number], TProviderOptions extends
|
|
37
|
+
export declare class GeminiTextAdapter<TModel extends (typeof GEMINI_MODELS)[number], TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>, TInputModalities extends ReadonlyArray<Modality> = ResolveInputModalities<TModel>, TToolCapabilities extends ReadonlyArray<string> = ResolveToolCapabilities<TModel>> extends BaseTextAdapter<TModel, TProviderOptions, TInputModalities, GeminiMessageMetadataByModality, TToolCapabilities> {
|
|
33
38
|
readonly kind: "text";
|
|
34
39
|
readonly name: "gemini";
|
|
35
40
|
private client;
|
|
@@ -64,10 +69,10 @@ export declare class GeminiTextAdapter<TModel extends (typeof GEMINI_MODELS)[num
|
|
|
64
69
|
* Creates a Gemini text adapter with explicit API key.
|
|
65
70
|
* Type resolution happens here at the call site.
|
|
66
71
|
*/
|
|
67
|
-
export declare function createGeminiChat<TModel extends (typeof GEMINI_MODELS)[number]>(model: TModel, apiKey: string, config?: Omit<GeminiTextConfig, 'apiKey'>): GeminiTextAdapter<TModel, ResolveProviderOptions<TModel>, ResolveInputModalities<TModel>>;
|
|
72
|
+
export declare function createGeminiChat<TModel extends (typeof GEMINI_MODELS)[number]>(model: TModel, apiKey: string, config?: Omit<GeminiTextConfig, 'apiKey'>): GeminiTextAdapter<TModel, ResolveProviderOptions<TModel>, ResolveInputModalities<TModel>, ResolveToolCapabilities<TModel>>;
|
|
68
73
|
/**
|
|
69
74
|
* Creates a Gemini text adapter with automatic API key detection.
|
|
70
75
|
* Type resolution happens here at the call site.
|
|
71
76
|
*/
|
|
72
|
-
export declare function geminiText<TModel extends (typeof GEMINI_MODELS)[number]>(model: TModel, config?: Omit<GeminiTextConfig, 'apiKey'>): GeminiTextAdapter<TModel, ResolveProviderOptions<TModel>, ResolveInputModalities<TModel>>;
|
|
77
|
+
export declare function geminiText<TModel extends (typeof GEMINI_MODELS)[number]>(model: TModel, config?: Omit<GeminiTextConfig, 'apiKey'>): GeminiTextAdapter<TModel, ResolveProviderOptions<TModel>, ResolveInputModalities<TModel>, ResolveToolCapabilities<TModel>>;
|
|
73
78
|
export {};
|
|
@@ -12,11 +12,20 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
12
12
|
}
|
|
13
13
|
async *chatStream(options) {
|
|
14
14
|
const mappedOptions = this.mapCommonOptionsToGemini(options);
|
|
15
|
+
const { logger } = options;
|
|
15
16
|
try {
|
|
17
|
+
logger.request(
|
|
18
|
+
`activity=chat provider=gemini model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,
|
|
19
|
+
{ provider: "gemini", model: this.model }
|
|
20
|
+
);
|
|
16
21
|
const result = await this.client.models.generateContentStream(mappedOptions);
|
|
17
|
-
yield* this.processStreamChunks(result, options);
|
|
22
|
+
yield* this.processStreamChunks(result, options, logger);
|
|
18
23
|
} catch (error) {
|
|
19
24
|
const timestamp = Date.now();
|
|
25
|
+
logger.errors("gemini.chatStream fatal", {
|
|
26
|
+
error,
|
|
27
|
+
source: "gemini.chatStream"
|
|
28
|
+
});
|
|
20
29
|
yield asChunk({
|
|
21
30
|
type: "RUN_ERROR",
|
|
22
31
|
model: options.model,
|
|
@@ -35,8 +44,13 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
35
44
|
*/
|
|
36
45
|
async structuredOutput(options) {
|
|
37
46
|
const { chatOptions, outputSchema } = options;
|
|
47
|
+
const { logger } = chatOptions;
|
|
38
48
|
const mappedOptions = this.mapCommonOptionsToGemini(chatOptions);
|
|
39
49
|
try {
|
|
50
|
+
logger.request(
|
|
51
|
+
`activity=chat provider=gemini model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,
|
|
52
|
+
{ provider: "gemini", model: this.model }
|
|
53
|
+
);
|
|
40
54
|
const result = await this.client.models.generateContent({
|
|
41
55
|
...mappedOptions,
|
|
42
56
|
config: {
|
|
@@ -59,6 +73,10 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
59
73
|
rawText
|
|
60
74
|
};
|
|
61
75
|
} catch (error) {
|
|
76
|
+
logger.errors("gemini.structuredOutput fatal", {
|
|
77
|
+
error,
|
|
78
|
+
source: "gemini.structuredOutput"
|
|
79
|
+
});
|
|
62
80
|
throw new Error(
|
|
63
81
|
error instanceof Error ? error.message : "An unknown error occurred during structured output generation."
|
|
64
82
|
);
|
|
@@ -78,7 +96,7 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
78
96
|
}
|
|
79
97
|
return textContent;
|
|
80
98
|
}
|
|
81
|
-
async *processStreamChunks(result, options) {
|
|
99
|
+
async *processStreamChunks(result, options, logger) {
|
|
82
100
|
const model = options.model;
|
|
83
101
|
const timestamp = Date.now();
|
|
84
102
|
let accumulatedContent = "";
|
|
@@ -95,6 +113,7 @@ class GeminiTextAdapter extends BaseTextAdapter {
|
|
|
95
113
|
let hasEmittedTextMessageStart = false;
|
|
96
114
|
let hasEmittedStepStarted = false;
|
|
97
115
|
for await (const chunk of result) {
|
|
116
|
+
logger.provider(`provider=gemini`, { chunk });
|
|
98
117
|
if (!hasEmittedRunStarted) {
|
|
99
118
|
hasEmittedRunStarted = true;
|
|
100
119
|
yield asChunk({
|