# Warlock AI Google — full skills

> Package: `@warlock.js/ai-google`

> Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/ai-google/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.

## setup-google  `@warlock.js/ai-google/setup-google/SKILL.md`

---
name: setup-google
description: 'Wire @warlock.js/ai-google — new GoogleSDK({apiKey} | {vertexai, project, location}) for Gemini API + Vertex AI. generateContent / embedContent + thoughtSignature round-trip for thinking models, batched embeddings. Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `thoughtSignature`, `responseJsonSchema`, `vertexai`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: agent loop `@warlock.js/ai/run-ai-agent/SKILL.md`; provider picking `@warlock.js/ai/pick-ai-provider/SKILL.md`; embedder usage `@warlock.js/ai/embed-text/SKILL.md`; siblings `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-ollama`; raw `@google/genai`, `@google-cloud/vertexai`, Vercel `@ai-sdk/google`.'
---

# `@warlock.js/ai-google`

Provider adapter that turns Google Gemini into a vendor-neutral `ModelContract`, plus a Gemini embedder. Mirrors the openai / anthropic / bedrock adapters.

## Construction

```ts
import { GoogleSDK } from "@warlock.js/ai-google";

// Gemini API (API key):
const google = new GoogleSDK({ apiKey: process.env.GEMINI_API_KEY! });

// Vertex AI:
const vertex = new GoogleSDK({
  vertexai: true,
  project: "my-project",
  location: "us-central1",
  provider: "vertex",
});
```

`GoogleSDK` is a class with a long-lived `GoogleGenAI` client. The two adapter-only keys (`provider`, `pricing`) are stripped; everything else is forwarded verbatim to `new GoogleGenAI(...)` (so `apiVersion`, `httpOptions`, `vertexai`, `project`, `location` all pass through). `provider` defaults to `"google"`.

## Producing a model

```ts
google.model({ name: "gemini-2.5-flash" })
google.model({ name: "gemini-2.5-pro", temperature: 0.2 })
google.model({ name: "gemini-1.5-flash", maxTokens: 2048 })
```

## Capabilities — what's auto-set

| Flag | Default |
| --- | --- |
| `structuredOutput` | `true` (via `responseMimeType` + `responseJsonSchema`) |
| `vision` | Inferred from model id substring. `true` when the id contains `gemini-1.5`, `gemini-2` (covers 2.x / 2.5), `gemini-exp`, `gemini-flash`, or `gemini-pro-vision`; `false` for bare `gemini-pro` / `gemini-1.0-pro` and any unknown id. Case-insensitive, tolerates date/preview/vendor-prefix suffixes (`models/gemini-1.5-flash-001`). |

Explicit config always wins.

## System prompt

Gemini content roles must be `"user"` or `"model"` — there is no `"system"` role. The adapter hoists every neutral `role: "system"` message into the separate `config.systemInstruction` string. Transparent to the agent.

## Roles & tool calls

- Neutral `assistant` → Gemini `"model"`; `user` stays `"user"`.
- Assistant tool calls → `"model"` content: optional leading `{ text }` + one `{ functionCall: { name, args } }` part per call (`id` deliberately omitted — see below).
- Tool results (`role: "tool"`) → a `"user"` content with one `{ functionResponse: { name, response } }` part. `response` must be a JSON object, so a stringified-JSON tool result is parsed back to an object; a non-object result is wrapped as `{ result: <raw> }`.

**Gemini matches a result to its call by name, not id** — the Developer API assigns no function-call ids. The adapter sets neutral `id` = tool name so `toolCallId` is non-empty. The wire `id` is omitted from both `functionCall` and `functionResponse` (echoing an empty/synthetic id is rejected by Gemini). **Limitation:** two parallel calls to the same tool in one turn share an id — inherent to the Developer API's id-less design.

**Gemini reports `finishReason: STOP` even when it called a function** — the adapter overrides to `"tool_calls"` when the response carries function calls.

**`thoughtSignature` round-trip (thinking models).** Gemini 2.5 thinking models attach an opaque `thoughtSignature` to each `functionCall` part and **400 the next request** if it's not echoed back. The adapter captures the signature into `ModelToolCallRequest.providerMetadata.thoughtSignature` and replays it on the echoed `functionCall` part. Fully automatic.

## Structured output

Object-root `responseSchema` + `structuredOutput`-capable → `config.responseMimeType = "application/json"` + `config.responseJsonSchema = <schema>` (Gemini takes a **raw JSON Schema** directly, not its typed `Schema`).

## Multipart messages (vision)

- `{ type: "text" }` → `{ text }`
- `{ type: "image", source: { base64, mediaType } }` → `{ inlineData: { mimeType, data } }`
- `{ type: "image", source: { url } }` → **throws `InvalidRequestError`**. `generateContent` does not fetch arbitrary remote URLs (only Files API / GCS URIs). Resolve images to base64 first.

## Streaming

`model.stream()` drains `generateContentStream`. Each chunk's `.text` → `{ type: "delta" }`; `functionCall` parts (read from `candidates[0].content.parts`, not the `.functionCalls` getter — the getter drops `thoughtSignature`) are emitted as `{ type: "tool-call" }` **fully formed** (Gemini streams a complete call with parsed `args`, not partial JSON), carrying `providerMetadata.thoughtSignature` when present. Terminal `{ type: "done", finishReason, usage }` — usage from the final chunk's `usageMetadata`.

## Finish-reason mapping

`STOP` → `stop` · `MAX_TOKENS` → `length` · `SAFETY` / `RECITATION` / `BLOCKLIST` / `PROHIBITED_CONTENT` / `MALFORMED_FUNCTION_CALL` / unknown / null → `error`. `tool_calls` is derived from function-call presence.

## Embeddings

```ts
const embedder = google.embedder({ name: "gemini-embedding-001" });
const { vector } = await embedder.embed("Hello world");
const { vectors } = await embedder.embedMany(["a", "b"]);   // single batched call
```

`embedContent` accepts an array natively, so `embedMany` is **one request** (unlike Bedrock/Titan). Pass `dimensions` to forward Gemini's `outputDimensionality` truncation hint.

**Gemini's embed endpoint returns no token usage**, so `usage` is always `{ promptTokens: 0, totalTokens: 0 }` (honest absence).

## Errors

Wrapped into the typed `@warlock.js/ai` `AIError` hierarchy. Gemini has no machine error code — dispatch keys on HTTP `status` + canonical status phrase in the message:

- 401/403 / `PERMISSION_DENIED` / "API key not valid" → `ProviderAuthError`
- 429 / `RESOURCE_EXHAUSTED` → `ProviderRateLimitError`
- 400 with token/context phrasing → `ContextLengthExceededError`, else `InvalidRequestError`
- 404 / any other 4xx → `InvalidRequestError`
- 504 / `DEADLINE_EXCEEDED` / `ETIMEDOUT` / `ECONNABORTED` / `AbortError` → `ProviderTimeoutError`
- anything else (5xx and status-less) → `ProviderError`

Timeout is checked first, so an aborted/`AbortError` request always classifies as `ProviderTimeoutError` regardless of status. `AIError` instances pass through unwrapped (no double-wrap).

## Token counting

```ts
await google.count("some text")  // approximate heuristic, offline
```

`count(text, model?)` accepts an optional model arg for signature parity but **ignores it** — the estimate is `approximateTokenCount` (≈ `ceil(chars / 4)`) from `@warlock.js/ai`, never a `countTokens` network call.

## Token usage

`complete()` / `stream()` map Gemini's `usageMetadata` into the neutral `Usage` (`{ input, output, total }`): `promptTokenCount` → `input`, `candidatesTokenCount` → `output`, `totalTokenCount` → `total` (falling back to `input + output` when absent). `cachedContentTokenCount`, when > 0, is surfaced as `usage.cachedTokens`. Embeddings have no usage (see above).

## Pricing

Optional USD pricing flows in two ways, resolved per `model()` call:

```ts
// SDK-level registry, keyed by model name:
const google = new GoogleSDK({
  apiKey,
  pricing: { "gemini-2.5-flash": { input: 0.3, output: 2.5 } },
});

google.model({ name: "gemini-2.5-flash" });               // → inherits the registry entry
google.model({ name: "gemini-2.5-flash", pricing: {...} }); // → per-model pricing wins
google.model({ name: "gemini-2.5-pro" });                  // → no entry → pricing undefined (no cost computed)
```

Resolution order: per-model `pricing` > SDK-level registry entry > `undefined`. The resolved `ModelPricing` is attached to the produced model so the agent runtime can compute cost from usage.

## When NOT to use this skill

- Direct `@google/genai` calls without going through `@warlock.js/ai` agents.
- OpenAI / Anthropic / Bedrock / Ollama models — those have their own adapter packages.

## See also

- [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md)
- [`@warlock.js/ai/pick-ai-provider/SKILL.md`](@warlock.js/ai/pick-ai-provider/SKILL.md)
- [`@warlock.js/ai/embed-text/SKILL.md`](@warlock.js/ai/embed-text/SKILL.md)


