@tanstack/ai-gemini 0.0.2 → 0.1.0

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.
Files changed (41) hide show
  1. package/README.md +26 -0
  2. package/dist/esm/adapters/image.d.ts +83 -0
  3. package/dist/esm/adapters/image.js +60 -0
  4. package/dist/esm/adapters/image.js.map +1 -0
  5. package/dist/esm/adapters/summarize.d.ts +53 -0
  6. package/dist/esm/adapters/summarize.js +139 -0
  7. package/dist/esm/adapters/summarize.js.map +1 -0
  8. package/dist/esm/adapters/text.d.ts +63 -0
  9. package/dist/esm/{gemini-adapter.js → adapters/text.js} +86 -132
  10. package/dist/esm/adapters/text.js.map +1 -0
  11. package/dist/esm/adapters/tts.d.ts +129 -0
  12. package/dist/esm/adapters/tts.js +78 -0
  13. package/dist/esm/adapters/tts.js.map +1 -0
  14. package/dist/esm/image/image-provider-options.d.ts +139 -0
  15. package/dist/esm/image/image-provider-options.js +53 -0
  16. package/dist/esm/image/image-provider-options.js.map +1 -0
  17. package/dist/esm/index.d.ts +15 -2
  18. package/dist/esm/index.js +22 -4
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/model-meta.d.ts +128 -1
  21. package/dist/esm/model-meta.js +71 -5
  22. package/dist/esm/model-meta.js.map +1 -1
  23. package/dist/esm/tools/tool-converter.js +5 -3
  24. package/dist/esm/tools/tool-converter.js.map +1 -1
  25. package/dist/esm/utils/client.d.ts +17 -0
  26. package/dist/esm/utils/client.js +25 -0
  27. package/dist/esm/utils/client.js.map +1 -0
  28. package/dist/esm/utils/index.d.ts +1 -0
  29. package/package.json +5 -4
  30. package/src/adapters/image.ts +188 -0
  31. package/src/adapters/summarize.ts +242 -0
  32. package/src/{gemini-adapter.ts → adapters/text.ts} +196 -245
  33. package/src/adapters/tts.ts +223 -0
  34. package/src/image/image-provider-options.ts +239 -0
  35. package/src/index.ts +66 -2
  36. package/src/model-meta.ts +87 -62
  37. package/src/tools/tool-converter.ts +6 -5
  38. package/src/utils/client.ts +43 -0
  39. package/src/utils/index.ts +6 -0
  40. package/dist/esm/gemini-adapter.d.ts +0 -71
  41. package/dist/esm/gemini-adapter.js.map +0 -1
package/README.md CHANGED
@@ -38,7 +38,9 @@
38
38
  A powerful, type-safe AI SDK for building AI-powered applications.
39
39
 
40
40
  - Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
41
+ - **Tree-shakeable adapters** - Import only what you need for smaller bundles
41
42
  - **Multimodal content support** - Send images, audio, video, and documents
43
+ - **Image generation** - Generate images with OpenAI DALL-E/GPT-Image and Gemini Imagen
42
44
  - Chat completion, streaming, and agent loop strategies
43
45
  - Headless chat state management with adapters (SSE, HTTP stream, custom)
44
46
  - Isomorphic type-safe tools with server/client execution
@@ -46,6 +48,30 @@ A powerful, type-safe AI SDK for building AI-powered applications.
46
48
 
47
49
  ### <a href="https://tanstack.com/ai">Read the docs →</b></a>
48
50
 
51
+ ## Tree-Shakeable Adapters
52
+
53
+ Import only the functionality you need for smaller bundle sizes:
54
+
55
+ ```typescript
56
+ // Only chat functionality - no summarization code bundled
57
+ import { openaiText } from '@tanstack/ai-openai/adapters'
58
+ import { generate } from '@tanstack/ai'
59
+
60
+ const textAdapter = openaiText()
61
+
62
+ const result = generate({
63
+ adapter: textAdapter,
64
+ model: 'gpt-4o',
65
+ messages: [{ role: 'user', content: [{ type: 'text', content: 'Hello!' }] }],
66
+ })
67
+
68
+ for await (const chunk of result) {
69
+ console.log(chunk)
70
+ }
71
+ ```
72
+
73
+ Available adapters: `openaiText`, `openaiEmbed`, `openaiSummarize`, `anthropicText`, `geminiText`, `ollamaText`, and more.
74
+
49
75
  ## Bonus: TanStack Start Integration
50
76
 
51
77
  TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).
@@ -0,0 +1,83 @@
1
+ import { BaseImageAdapter } from '@tanstack/ai/adapters';
2
+ import { GEMINI_IMAGE_MODELS } from '../model-meta.js';
3
+ import { GeminiImageModelProviderOptionsByName, GeminiImageModelSizeByName, GeminiImageProviderOptions } from '../image/image-provider-options.js';
4
+ import { ImageGenerationOptions, ImageGenerationResult } from '@tanstack/ai';
5
+ import { GeminiClientConfig } from '../utils.js';
6
+ /**
7
+ * Configuration for Gemini image adapter
8
+ */
9
+ export interface GeminiImageConfig extends GeminiClientConfig {
10
+ }
11
+ /** Model type for Gemini Image */
12
+ export type GeminiImageModel = (typeof GEMINI_IMAGE_MODELS)[number];
13
+ /**
14
+ * Gemini Image Generation Adapter
15
+ *
16
+ * Tree-shakeable adapter for Gemini Imagen image generation functionality.
17
+ * Supports Imagen 3 and Imagen 4 models.
18
+ *
19
+ * Features:
20
+ * - Aspect ratio-based image sizing
21
+ * - Person generation controls
22
+ * - Safety filtering
23
+ * - Watermark options
24
+ */
25
+ export declare class GeminiImageAdapter<TModel extends GeminiImageModel> extends BaseImageAdapter<TModel, GeminiImageProviderOptions, GeminiImageModelProviderOptionsByName, GeminiImageModelSizeByName> {
26
+ readonly kind: "image";
27
+ readonly name: "gemini";
28
+ '~types': {
29
+ providerOptions: GeminiImageProviderOptions;
30
+ modelProviderOptionsByName: GeminiImageModelProviderOptionsByName;
31
+ modelSizeByName: GeminiImageModelSizeByName;
32
+ };
33
+ private client;
34
+ constructor(config: GeminiImageConfig, model: TModel);
35
+ generateImages(options: ImageGenerationOptions<GeminiImageProviderOptions>): Promise<ImageGenerationResult>;
36
+ private buildConfig;
37
+ private transformResponse;
38
+ }
39
+ /**
40
+ * Creates a Gemini image adapter with explicit API key.
41
+ * Type resolution happens here at the call site.
42
+ *
43
+ * @param model - The model name (e.g., 'imagen-3.0-generate-002')
44
+ * @param apiKey - Your Google API key
45
+ * @param config - Optional additional configuration
46
+ * @returns Configured Gemini image adapter instance with resolved types
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const adapter = createGeminiImage('imagen-3.0-generate-002', "your-api-key");
51
+ *
52
+ * const result = await generateImage({
53
+ * adapter,
54
+ * prompt: 'A cute baby sea otter'
55
+ * });
56
+ * ```
57
+ */
58
+ export declare function createGeminiImage<TModel extends GeminiImageModel>(model: TModel, apiKey: string, config?: Omit<GeminiImageConfig, 'apiKey'>): GeminiImageAdapter<TModel>;
59
+ /**
60
+ * Creates a Gemini image adapter with automatic API key detection from environment variables.
61
+ * Type resolution happens here at the call site.
62
+ *
63
+ * Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:
64
+ * - `process.env` (Node.js)
65
+ * - `window.env` (Browser with injected env)
66
+ *
67
+ * @param model - The model name (e.g., 'imagen-4.0-generate-001')
68
+ * @param config - Optional configuration (excluding apiKey which is auto-detected)
69
+ * @returns Configured Gemini image adapter instance with resolved types
70
+ * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * // Automatically uses GOOGLE_API_KEY from environment
75
+ * const adapter = geminiImage('imagen-4.0-generate-001');
76
+ *
77
+ * const result = await generateImage({
78
+ * adapter,
79
+ * prompt: 'A beautiful sunset over mountains'
80
+ * });
81
+ * ```
82
+ */
83
+ export declare function geminiImage<TModel extends GeminiImageModel>(model: TModel, config?: Omit<GeminiImageConfig, 'apiKey'>): GeminiImageAdapter<TModel>;
@@ -0,0 +1,60 @@
1
+ import { BaseImageAdapter } from "@tanstack/ai/adapters";
2
+ import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "../utils/client.js";
3
+ import { validatePrompt, validateImageSize, validateNumberOfImages, sizeToAspectRatio } from "../image/image-provider-options.js";
4
+ class GeminiImageAdapter extends BaseImageAdapter {
5
+ constructor(config, model) {
6
+ super({}, model);
7
+ this.kind = "image";
8
+ this.name = "gemini";
9
+ this.client = createGeminiClient(config);
10
+ }
11
+ async generateImages(options) {
12
+ const { model, prompt, numberOfImages, size } = options;
13
+ validatePrompt({ prompt, model });
14
+ validateImageSize(model, size);
15
+ validateNumberOfImages(model, numberOfImages);
16
+ const config = this.buildConfig(options);
17
+ const response = await this.client.models.generateImages({
18
+ model,
19
+ prompt,
20
+ config
21
+ });
22
+ return this.transformResponse(model, response);
23
+ }
24
+ buildConfig(options) {
25
+ const { size, numberOfImages, modelOptions } = options;
26
+ return {
27
+ numberOfImages: numberOfImages ?? 1,
28
+ // Map size to aspect ratio if provided (modelOptions.aspectRatio will override)
29
+ aspectRatio: size ? sizeToAspectRatio(size) : void 0,
30
+ ...modelOptions
31
+ };
32
+ }
33
+ transformResponse(model, response) {
34
+ const images = (response.generatedImages ?? []).map(
35
+ (item) => ({
36
+ b64Json: item.image?.imageBytes,
37
+ revisedPrompt: item.enhancedPrompt
38
+ })
39
+ );
40
+ return {
41
+ id: generateId(this.name),
42
+ model,
43
+ images,
44
+ usage: void 0
45
+ };
46
+ }
47
+ }
48
+ function createGeminiImage(model, apiKey, config) {
49
+ return new GeminiImageAdapter({ apiKey, ...config }, model);
50
+ }
51
+ function geminiImage(model, config) {
52
+ const apiKey = getGeminiApiKeyFromEnv();
53
+ return createGeminiImage(model, apiKey, config);
54
+ }
55
+ export {
56
+ GeminiImageAdapter,
57
+ createGeminiImage,
58
+ geminiImage
59
+ };
60
+ //# sourceMappingURL=image.js.map
@@ -0,0 +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 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 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 Imagen image generation functionality.\n * Supports Imagen 3 and Imagen 4 models.\n *\n * Features:\n * - Aspect ratio-based image sizing\n * - Person generation controls\n * - Safety filtering\n * - Watermark options\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, numberOfImages, size } = options\n\n // Validate inputs\n validatePrompt({ prompt, model })\n validateImageSize(model, size)\n validateNumberOfImages(model, numberOfImages)\n\n // Build request config\n const config = this.buildConfig(options)\n\n const response = await this.client.models.generateImages({\n model,\n prompt,\n config,\n })\n\n return this.transformResponse(model, response)\n }\n\n private buildConfig(\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 transformResponse(\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":";;;AAkDO,MAAM,2BAEH,iBAKR;AAAA,EAaA,YAAY,QAA2B,OAAe;AACpD,UAAM,CAAA,GAAI,KAAK;AAbjB,SAAS,OAAO;AAChB,SAAS,OAAO;AAad,SAAK,SAAS,mBAAmB,MAAM;AAAA,EACzC;AAAA,EAEA,MAAM,eACJ,SACgC;AAChC,UAAM,EAAE,OAAO,QAAQ,gBAAgB,SAAS;AAGhD,mBAAe,EAAE,QAAQ,OAAO;AAChC,sBAAkB,OAAO,IAAI;AAC7B,2BAAuB,OAAO,cAAc;AAG5C,UAAM,SAAS,KAAK,YAAY,OAAO;AAEvC,UAAM,WAAW,MAAM,KAAK,OAAO,OAAO,eAAe;AAAA,MACvD;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAED,WAAO,KAAK,kBAAkB,OAAO,QAAQ;AAAA,EAC/C;AAAA,EAEQ,YACN,SACsB;AACtB,UAAM,EAAE,MAAM,gBAAgB,aAAA,IAAiB;AAE/C,WAAO;AAAA,MACL,gBAAgB,kBAAkB;AAAA;AAAA,MAElC,aAAa,OAAO,kBAAkB,IAAI,IAAI;AAAA,MAC9C,GAAG;AAAA,IAAA;AAAA,EAEP;AAAA,EAEQ,kBACN,OACA,UACuB;AACvB,UAAM,UAAiC,SAAS,mBAAmB,CAAA,GAAI;AAAA,MACrE,CAAC,UAAU;AAAA,QACT,SAAS,KAAK,OAAO;AAAA,QACrB,eAAe,KAAK;AAAA,MAAA;AAAA,IACtB;AAGF,WAAO;AAAA,MACL,IAAI,WAAW,KAAK,IAAI;AAAA,MACxB;AAAA,MACA;AAAA,MACA,OAAO;AAAA,IAAA;AAAA,EAEX;AACF;AAqBO,SAAS,kBACd,OACA,QACA,QAC4B;AAC5B,SAAO,IAAI,mBAAmB,EAAE,QAAQ,GAAG,OAAA,GAAU,KAAK;AAC5D;AA0BO,SAAS,YACd,OACA,QAC4B;AAC5B,QAAM,SAAS,uBAAA;AACf,SAAO,kBAAkB,OAAO,QAAQ,MAAM;AAChD;"}
@@ -0,0 +1,53 @@
1
+ import { GoogleGenAI } from '@google/genai';
2
+ import { SummarizeAdapter } from '@tanstack/ai/adapters';
3
+ import { StreamChunk, SummarizationOptions, SummarizationResult } from '@tanstack/ai';
4
+ /**
5
+ * Available Gemini models for summarization
6
+ */
7
+ export declare const GeminiSummarizeModels: readonly ["gemini-2.0-flash", "gemini-1.5-flash", "gemini-1.5-pro", "gemini-2.0-flash-lite"];
8
+ export type GeminiSummarizeModel = (typeof GeminiSummarizeModels)[number];
9
+ /**
10
+ * Provider-specific options for Gemini summarization
11
+ */
12
+ export interface GeminiSummarizeProviderOptions {
13
+ /** Generation configuration */
14
+ generationConfig?: {
15
+ temperature?: number;
16
+ topP?: number;
17
+ topK?: number;
18
+ maxOutputTokens?: number;
19
+ stopSequences?: Array<string>;
20
+ };
21
+ /** Safety settings */
22
+ safetySettings?: Array<{
23
+ category: string;
24
+ threshold: string;
25
+ }>;
26
+ }
27
+ export interface GeminiSummarizeAdapterOptions {
28
+ }
29
+ /**
30
+ * Gemini Summarize Adapter
31
+ * A tree-shakeable summarization adapter for Google Gemini
32
+ */
33
+ export declare class GeminiSummarizeAdapter<TModel extends GeminiSummarizeModel> implements SummarizeAdapter<TModel, GeminiSummarizeProviderOptions> {
34
+ readonly kind: "summarize";
35
+ readonly name: "gemini";
36
+ readonly model: TModel;
37
+ '~types': {
38
+ providerOptions: GeminiSummarizeProviderOptions;
39
+ };
40
+ private client;
41
+ constructor(apiKeyOrClient: string | GoogleGenAI, model: TModel, _options?: GeminiSummarizeAdapterOptions);
42
+ summarize(options: SummarizationOptions): Promise<SummarizationResult>;
43
+ summarizeStream(options: SummarizationOptions): AsyncIterable<StreamChunk>;
44
+ private getFormatInstructions;
45
+ }
46
+ /**
47
+ * Creates a Gemini summarize adapter with explicit API key and model
48
+ */
49
+ export declare function createGeminiSummarize<TModel extends GeminiSummarizeModel>(apiKey: string, model: TModel, options?: GeminiSummarizeAdapterOptions): GeminiSummarizeAdapter<TModel>;
50
+ /**
51
+ * Creates a Gemini summarize adapter with API key from environment and required model
52
+ */
53
+ export declare function geminiSummarize<TModel extends GeminiSummarizeModel>(model: TModel, options?: GeminiSummarizeAdapterOptions): GeminiSummarizeAdapter<TModel>;
@@ -0,0 +1,139 @@
1
+ import { FinishReason } from "@google/genai";
2
+ import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "../utils/client.js";
3
+ const GeminiSummarizeModels = [
4
+ "gemini-2.0-flash",
5
+ "gemini-1.5-flash",
6
+ "gemini-1.5-pro",
7
+ "gemini-2.0-flash-lite"
8
+ ];
9
+ class GeminiSummarizeAdapter {
10
+ constructor(apiKeyOrClient, model, _options = {}) {
11
+ this.kind = "summarize";
12
+ this.name = "gemini";
13
+ this.client = typeof apiKeyOrClient === "string" ? createGeminiClient({ apiKey: apiKeyOrClient }) : apiKeyOrClient;
14
+ this.model = model;
15
+ }
16
+ async summarize(options) {
17
+ const model = options.model;
18
+ const formatInstructions = this.getFormatInstructions(options.style);
19
+ const lengthInstructions = options.maxLength ? ` Keep the summary under ${options.maxLength} tokens.` : "";
20
+ const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`;
21
+ const response = await this.client.models.generateContent({
22
+ model,
23
+ contents: [
24
+ {
25
+ role: "user",
26
+ parts: [
27
+ { text: `Please summarize the following:
28
+
29
+ ${options.text}` }
30
+ ]
31
+ }
32
+ ],
33
+ config: {
34
+ systemInstruction: systemPrompt
35
+ }
36
+ });
37
+ const summary = response.text ?? "";
38
+ const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
39
+ const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
40
+ return {
41
+ id: generateId("sum"),
42
+ model,
43
+ summary,
44
+ usage: {
45
+ promptTokens: inputTokens,
46
+ completionTokens: outputTokens,
47
+ totalTokens: inputTokens + outputTokens
48
+ }
49
+ };
50
+ }
51
+ async *summarizeStream(options) {
52
+ const model = options.model;
53
+ const id = generateId("sum");
54
+ let accumulatedContent = "";
55
+ let inputTokens = 0;
56
+ let outputTokens = 0;
57
+ const formatInstructions = this.getFormatInstructions(options.style);
58
+ const lengthInstructions = options.maxLength ? ` Keep the summary under ${options.maxLength} words.` : "";
59
+ const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`;
60
+ const result = await this.client.models.generateContentStream({
61
+ model,
62
+ contents: [
63
+ {
64
+ role: "user",
65
+ parts: [
66
+ { text: `Please summarize the following:
67
+
68
+ ${options.text}` }
69
+ ]
70
+ }
71
+ ],
72
+ config: {
73
+ systemInstruction: systemPrompt
74
+ }
75
+ });
76
+ for await (const chunk of result) {
77
+ if (chunk.usageMetadata) {
78
+ inputTokens = chunk.usageMetadata.promptTokenCount ?? inputTokens;
79
+ outputTokens = chunk.usageMetadata.candidatesTokenCount ?? outputTokens;
80
+ }
81
+ if (chunk.candidates?.[0]?.content?.parts) {
82
+ for (const part of chunk.candidates[0].content.parts) {
83
+ if (part.text) {
84
+ accumulatedContent += part.text;
85
+ yield {
86
+ type: "content",
87
+ id,
88
+ model,
89
+ timestamp: Date.now(),
90
+ delta: part.text,
91
+ content: accumulatedContent,
92
+ role: "assistant"
93
+ };
94
+ }
95
+ }
96
+ }
97
+ const finishReason = chunk.candidates?.[0]?.finishReason;
98
+ if (finishReason === FinishReason.STOP || finishReason === FinishReason.MAX_TOKENS || finishReason === FinishReason.SAFETY) {
99
+ yield {
100
+ type: "done",
101
+ id,
102
+ model,
103
+ timestamp: Date.now(),
104
+ finishReason: finishReason === FinishReason.STOP ? "stop" : finishReason === FinishReason.MAX_TOKENS ? "length" : "content_filter",
105
+ usage: {
106
+ promptTokens: inputTokens,
107
+ completionTokens: outputTokens,
108
+ totalTokens: inputTokens + outputTokens
109
+ }
110
+ };
111
+ }
112
+ }
113
+ }
114
+ getFormatInstructions(style) {
115
+ switch (style) {
116
+ case "bullet-points":
117
+ return "Provide the summary as bullet points.";
118
+ case "concise":
119
+ return "Provide a very brief one or two sentence summary.";
120
+ case "paragraph":
121
+ default:
122
+ return "Provide the summary in paragraph form.";
123
+ }
124
+ }
125
+ }
126
+ function createGeminiSummarize(apiKey, model, options) {
127
+ return new GeminiSummarizeAdapter(apiKey, model, options);
128
+ }
129
+ function geminiSummarize(model, options) {
130
+ const apiKey = getGeminiApiKeyFromEnv();
131
+ return new GeminiSummarizeAdapter(apiKey, model, options);
132
+ }
133
+ export {
134
+ GeminiSummarizeAdapter,
135
+ GeminiSummarizeModels,
136
+ createGeminiSummarize,
137
+ geminiSummarize
138
+ };
139
+ //# sourceMappingURL=summarize.js.map
@@ -0,0 +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'\n\nimport type { GoogleGenAI } from '@google/genai'\nimport type { SummarizeAdapter } from '@tanstack/ai/adapters'\nimport type {\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n} from '@tanstack/ai'\n\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(\n apiKeyOrClient: string | GoogleGenAI,\n model: TModel,\n _options: GeminiSummarizeAdapterOptions = {},\n ) {\n this.client =\n typeof apiKeyOrClient === 'string'\n ? createGeminiClient({ apiKey: apiKeyOrClient })\n : apiKeyOrClient\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: 'content',\n id,\n model,\n timestamp: Date.now(),\n delta: part.text,\n content: accumulatedContent,\n role: 'assistant',\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: 'done',\n 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 options?: GeminiSummarizeAdapterOptions,\n): GeminiSummarizeAdapter<TModel> {\n return new GeminiSummarizeAdapter(apiKey, model, options)\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 options?: GeminiSummarizeAdapterOptions,\n): GeminiSummarizeAdapter<TModel> {\n const apiKey = getGeminiApiKeyFromEnv()\n return new GeminiSummarizeAdapter(apiKey, model, options)\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from '@tanstack/ai/adapters';
2
+ import { GEMINI_MODELS, GeminiChatModelProviderOptionsByName, GeminiModelInputModalitiesByName } from '../model-meta.js';
3
+ import { Modality, StreamChunk, TextOptions } from '@tanstack/ai';
4
+ import { ExternalTextProviderOptions } from '../text/text-provider-options.js';
5
+ import { GeminiMessageMetadataByModality } from '../message-types.js';
6
+ import { GeminiClientConfig } from '../utils.js';
7
+ /**
8
+ * Configuration for Gemini text adapter
9
+ */
10
+ export interface GeminiTextConfig extends GeminiClientConfig {
11
+ }
12
+ /**
13
+ * Gemini-specific provider options for text/chat
14
+ */
15
+ export type GeminiTextProviderOptions = ExternalTextProviderOptions;
16
+ /**
17
+ * Resolve provider options for a specific model.
18
+ * If the model has explicit options in the map, use those; otherwise use base options.
19
+ */
20
+ type ResolveProviderOptions<TModel extends string> = TModel extends keyof GeminiChatModelProviderOptionsByName ? GeminiChatModelProviderOptionsByName[TModel] : GeminiTextProviderOptions;
21
+ /**
22
+ * Resolve input modalities for a specific model.
23
+ * If the model has explicit modalities in the map, use those; otherwise use all modalities.
24
+ */
25
+ type ResolveInputModalities<TModel extends string> = TModel extends keyof GeminiModelInputModalitiesByName ? GeminiModelInputModalitiesByName[TModel] : readonly ['text', 'image', 'audio', 'video', 'document'];
26
+ /**
27
+ * Gemini Text (Chat) Adapter
28
+ *
29
+ * Tree-shakeable adapter for Gemini chat/text completion functionality.
30
+ * Import only what you need for smaller bundle sizes.
31
+ */
32
+ export declare class GeminiTextAdapter<TModel extends (typeof GEMINI_MODELS)[number], TProviderOptions extends object = ResolveProviderOptions<TModel>, TInputModalities extends ReadonlyArray<Modality> = ResolveInputModalities<TModel>> extends BaseTextAdapter<TModel, TProviderOptions, TInputModalities, GeminiMessageMetadataByModality> {
33
+ readonly kind: "text";
34
+ readonly name: "gemini";
35
+ private client;
36
+ constructor(config: GeminiTextConfig, model: TModel);
37
+ chatStream(options: TextOptions<GeminiTextProviderOptions>): AsyncIterable<StreamChunk>;
38
+ /**
39
+ * Generate structured output using Gemini's native JSON response format.
40
+ * Uses responseMimeType: 'application/json' and responseSchema for structured output.
41
+ * The outputSchema is already JSON Schema (converted in the ai layer).
42
+ */
43
+ structuredOutput(options: StructuredOutputOptions<GeminiTextProviderOptions>): Promise<StructuredOutputResult<unknown>>;
44
+ /**
45
+ * Extract text content from a non-streaming response
46
+ */
47
+ private extractTextFromResponse;
48
+ private processStreamChunks;
49
+ private convertContentPartToGemini;
50
+ private formatMessages;
51
+ private mapCommonOptionsToGemini;
52
+ }
53
+ /**
54
+ * Creates a Gemini text adapter with explicit API key.
55
+ * Type resolution happens here at the call site.
56
+ */
57
+ export declare function createGeminiChat<TModel extends (typeof GEMINI_MODELS)[number]>(model: TModel, apiKey: string, config?: Omit<GeminiTextConfig, 'apiKey'>): GeminiTextAdapter<TModel, ResolveProviderOptions<TModel>, ResolveInputModalities<TModel>>;
58
+ /**
59
+ * Creates a Gemini text adapter with automatic API key detection.
60
+ * Type resolution happens here at the call site.
61
+ */
62
+ export declare function geminiText<TModel extends (typeof GEMINI_MODELS)[number]>(model: TModel, config?: Omit<GeminiTextConfig, 'apiKey'>): GeminiTextAdapter<TModel, ResolveProviderOptions<TModel>, ResolveInputModalities<TModel>>;
63
+ export {};