# Gemini Client

Interact with Google's Gemini API for multimodal AI capabilities including text, image, and video understanding.

## Methods

| Method                                   | Description                                        |
| ---------------------------------------- | -------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any Gemini API request with schema validation |

## Usage

### Generate Content (Text)

```typescript
import { api, z, gemini } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_GEMINI = "a1b2c3d4-5678-90ab-cdef-gemini000001";

const GenerateContentResponseSchema = z.object({
  candidates: z.array(
    z.object({
      content: z.object({
        parts: z.array(
          z.object({
            text: z.string(),
          }),
        ),
        role: z.string(),
      }),
      finishReason: z.string(),
      safetyRatings: z
        .array(
          z.object({
            category: z.string(),
            probability: z.string(),
          }),
        )
        .optional(),
    }),
  ),
  usageMetadata: z.object({
    promptTokenCount: z.number(),
    candidatesTokenCount: z.number(),
    totalTokenCount: z.number(),
  }),
});

export default api({
  integrations: {
    gemini: gemini(PROD_GEMINI),
  },
  name: "GeminiExample",
  input: z.object({
    prompt: z.string(),
  }),
  output: z.object({
    response: z.string(),
  }),
  async run(ctx, { prompt }) {
    const result = await ctx.integrations.gemini.apiRequest(
      {
        method: "POST",
        path: "/v1/models/gemini-1.5-pro:generateContent",
        body: {
          contents: [
            {
              parts: [{ text: prompt }],
            },
          ],
        },
      },
      { response: GenerateContentResponseSchema },
    );

    const text = result.candidates[0]?.content.parts[0]?.text ?? "";
    return { response: text };
  },
});
```

### Multimodal (Image + Text)

```typescript
const result = await ctx.integrations.gemini.apiRequest(
  {
    method: "POST",
    path: "/v1/models/gemini-1.5-pro:generateContent",
    body: {
      contents: [
        {
          parts: [
            {
              inline_data: {
                mime_type: "image/jpeg",
                data: base64ImageData, // Base64 encoded image
              },
            },
            {
              text: "What's in this image? Describe it in detail.",
            },
          ],
        },
      ],
    },
  },
  { response: GenerateContentResponseSchema },
);
```

### Multi-turn Conversation

```typescript
const result = await ctx.integrations.gemini.apiRequest(
  {
    method: "POST",
    path: "/v1/models/gemini-1.5-pro:generateContent",
    body: {
      contents: [
        {
          role: "user",
          parts: [{ text: "Hello, who are you?" }],
        },
        {
          role: "model",
          parts: [
            { text: "I'm Gemini, a large language model created by Google." },
          ],
        },
        {
          role: "user",
          parts: [{ text: "What can you help me with?" }],
        },
      ],
    },
  },
  { response: GenerateContentResponseSchema },
);
```

### Function Calling

```typescript
const FunctionCallResponseSchema = z.object({
  candidates: z.array(
    z.object({
      content: z.object({
        parts: z.array(
          z.union([
            z.object({ text: z.string() }),
            z.object({
              functionCall: z.object({
                name: z.string(),
                args: z.record(z.unknown()),
              }),
            }),
          ]),
        ),
        role: z.string(),
      }),
    }),
  ),
});

const result = await ctx.integrations.gemini.apiRequest(
  {
    method: "POST",
    path: "/v1/models/gemini-1.5-pro:generateContent",
    body: {
      contents: [
        {
          parts: [{ text: "What's the weather in Paris?" }],
        },
      ],
      tools: [
        {
          function_declarations: [
            {
              name: "get_weather",
              description: "Get the current weather for a location",
              parameters: {
                type: "object",
                properties: {
                  location: {
                    type: "string",
                    description: "The city name",
                  },
                },
                required: ["location"],
              },
            },
          ],
        },
      ],
    },
  },
  { response: FunctionCallResponseSchema },
);
```

### Generate with Safety Settings

```typescript
const result = await ctx.integrations.gemini.apiRequest(
  {
    method: "POST",
    path: "/v1/models/gemini-1.5-pro:generateContent",
    body: {
      contents: [
        {
          parts: [{ text: "Your prompt here" }],
        },
      ],
      safetySettings: [
        {
          category: "HARM_CATEGORY_HARASSMENT",
          threshold: "BLOCK_MEDIUM_AND_ABOVE",
        },
        {
          category: "HARM_CATEGORY_HATE_SPEECH",
          threshold: "BLOCK_MEDIUM_AND_ABOVE",
        },
      ],
      generationConfig: {
        temperature: 0.7,
        topP: 0.9,
        topK: 40,
        maxOutputTokens: 1024,
      },
    },
  },
  { response: GenerateContentResponseSchema },
);
```

### Count Tokens

```typescript
const CountTokensResponseSchema = z.object({
  totalTokens: z.number(),
});

const result = await ctx.integrations.gemini.apiRequest(
  {
    method: "POST",
    path: "/v1/models/gemini-1.5-pro:countTokens",
    body: {
      contents: [
        {
          parts: [{ text: "Your text to count tokens for" }],
        },
      ],
    },
  },
  { response: CountTokensResponseSchema },
);

console.log(`Total tokens: ${result.totalTokens}`);
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### Streaming Is Not Supported

`apiRequest()` does not support streaming or Server-Sent Events. Use
`:generateContent`, never `:streamGenerateContent` or `alt=sse` — streaming
responses fail schema validation. Every call returns the complete response;
if a UI needs real-time token streaming, handle it at the frontend layer,
not through the SDK.

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await gemini.generateContent({ ... });
await gemini.chat({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.gemini.apiRequest(
  { method: "POST", path: "/v1/models/gemini-1.5-pro:generateContent", body: { ... } },
  { response: GenerateContentResponseSchema }
);
```

### Model Name in Path

The model name is part of the URL path, not the body:

```typescript
// WRONG - Model in body
await ctx.integrations.gemini.apiRequest(
  {
    method: "POST",
    path: "/v1/generateContent",
    body: {
      model: "gemini-1.5-pro", // Wrong location
      contents: [...],
    },
  },
  { response: schema }
);

// CORRECT - Model in path
await ctx.integrations.gemini.apiRequest(
  {
    method: "POST",
    path: "/v1/models/gemini-1.5-pro:generateContent", // Model in URL
    body: {
      contents: [...],
    },
  },
  { response: schema }
);
```

### Content Structure

Gemini uses a specific content structure with `parts`:

```typescript
// WRONG - Simple string message
const body = {
  messages: [{ role: "user", content: "Hello" }],
};

// CORRECT - Parts-based structure
const body = {
  contents: [
    {
      role: "user",
      parts: [{ text: "Hello" }],
    },
  ],
};
```

### Role Names

Gemini uses "model" for assistant responses:

```typescript
// WRONG - Using "assistant"
const contents = [
  { role: "user", parts: [{ text: "Hi" }] },
  { role: "assistant", parts: [{ text: "Hello!" }] }, // Wrong role
];

// CORRECT - Using "model"
const contents = [
  { role: "user", parts: [{ text: "Hi" }] },
  { role: "model", parts: [{ text: "Hello!" }] }, // Correct role
];
```

### Safety Blocking

Responses may be blocked by safety filters. Check the `finishReason`:

```typescript
const result = await ctx.integrations.gemini.apiRequest(...);

if (result.candidates[0]?.finishReason === "SAFETY") {
  console.log("Response blocked by safety filters");
  console.log("Safety ratings:", result.candidates[0]?.safetyRatings);
}
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.gemini.apiRequest(
    { method: "POST", path: "/v1/models/gemini-1.5-pro:generateContent", body: { ... } },
    { response: GenerateContentResponseSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [Gemini API Documentation](https://ai.google.dev/docs)
- [Generate Content](https://ai.google.dev/api/rest/v1/models/generateContent)
- [Function Calling](https://ai.google.dev/docs/function_calling)
- [Safety Settings](https://ai.google.dev/docs/safety_setting_gemini)
