@warlock.js/ai-google 4.14.0 → 4.15.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.
- package/CHANGELOG.md +26 -1
- package/cjs/index.cjs +309 -37
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +11 -5
- package/esm/config.type.d.mts.map +1 -1
- package/esm/gemini-image.d.mts +91 -0
- package/esm/gemini-image.d.mts.map +1 -0
- package/esm/gemini-image.mjs +231 -0
- package/esm/gemini-image.mjs.map +1 -0
- package/esm/index.d.mts +2 -1
- package/esm/index.mjs +2 -1
- package/esm/model.mjs +7 -25
- package/esm/model.mjs.map +1 -1
- package/esm/sdk.d.mts +16 -8
- package/esm/sdk.d.mts.map +1 -1
- package/esm/sdk.mjs +48 -8
- package/esm/sdk.mjs.map +1 -1
- package/esm/utils/apply-google-usage.mjs +30 -0
- package/esm/utils/apply-google-usage.mjs.map +1 -0
- package/esm/utils/index.mjs +1 -0
- package/llms-full.txt +50 -6
- package/llms.txt +1 -1
- package/package.json +6 -6
- package/skills/setup-google/SKILL.md +50 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-google-usage.mjs","names":[],"sources":["../../../../../../../ai-google/src/utils/apply-google-usage.ts"],"sourcesContent":["import type { Usage } from \"@warlock.js/ai\";\nimport type { GenerateContentResponse } from \"@google/genai\";\n\n/** Gemini's per-response token accounting block, as the SDK types it. */\nexport type GoogleUsageMetadata = NonNullable<GenerateContentResponse[\"usageMetadata\"]>;\n\n/**\n * Fold a Gemini `usageMetadata` block into a running neutral `Usage`\n * accumulator. Shared by every `generateContent`-backed surface — the\n * chat model's `complete()`, its streaming loop (where the final chunk\n * carries cumulative totals), and the Gemini image model — so one\n * mapping decides what a Gemini token report means package-wide.\n *\n * Cache-read hits (`cachedContentTokenCount`, implicit or explicit\n * context caching) surface as `cachedTokens`; the thinking-phase tokens\n * of a reasoning model (`thoughtsTokenCount`) surface as\n * `reasoningTokens`. Both are emitted only when reported `> 0` so an\n * absent channel leaves the field undefined rather than a false zero.\n *\n * `total` falls back to `input + output` when Google omits\n * `totalTokenCount`.\n */\nexport function applyGoogleUsage(usage: Usage, raw: GoogleUsageMetadata): void {\n usage.input = raw.promptTokenCount ?? usage.input;\n usage.output = raw.candidatesTokenCount ?? usage.output;\n usage.total = raw.totalTokenCount ?? usage.input + usage.output;\n\n const cached = raw.cachedContentTokenCount;\n\n if (cached && cached > 0) {\n usage.cachedTokens = cached;\n }\n\n const reasoning = raw.thoughtsTokenCount;\n\n if (reasoning && reasoning > 0) {\n usage.reasoningTokens = reasoning;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAsBA,SAAgB,iBAAiB,OAAc,KAAgC;CAC7E,MAAM,QAAQ,IAAI,oBAAoB,MAAM;CAC5C,MAAM,SAAS,IAAI,wBAAwB,MAAM;CACjD,MAAM,QAAQ,IAAI,mBAAmB,MAAM,QAAQ,MAAM;CAEzD,MAAM,SAAS,IAAI;CAEnB,IAAI,UAAU,SAAS,GACrB,MAAM,eAAe;CAGvB,MAAM,YAAY,IAAI;CAEtB,IAAI,aAAa,YAAY,GAC3B,MAAM,kBAAkB;AAE5B"}
|
package/esm/utils/index.mjs
CHANGED
package/llms-full.txt
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
name: setup-google
|
|
11
|
-
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. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for
|
|
11
|
+
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. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for image generation via ai.image — the id picks the transport: gemini-* → generateContent + responseModalities IMAGE (usage passed through; generateImages is deprecated by Google), everything else → generateImages (Imagen, per-image, zero usage) — extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `gemini image`, `nano banana`, `responseModalities`, `imageConfig`, `ai.image`, `inlineData`, `pdf input`, `audio input`, `thoughtSignature`, `responseJsonSchema`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `promptCaching`, `cacheControl`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls", "gemini 2.5 thinking budget", "gemini cached content cost", "generate images with imagen", "generate an image with gemini", "gemini image model 404 not supported for predict", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — `@warlock.js/ai/generate-images/SKILL.md`; 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`.'
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# `@warlock.js/ai-google`
|
|
@@ -87,9 +87,47 @@ Gemini's multimodal input is **media-agnostic** — every binary modality maps t
|
|
|
87
87
|
|
|
88
88
|
PDF and audio reach the wire only when the model declares the matching capability (inferred for the multimodal Gemini families above) — so capability ≡ behavior.
|
|
89
89
|
|
|
90
|
-
## Image generation
|
|
90
|
+
## Image generation — two transports
|
|
91
91
|
|
|
92
|
-
`google.image({ name })` returns an `ImageModelContract`
|
|
92
|
+
`google.image({ name })` returns an `ImageModelContract` for the `ai.image()` verb. **The model id picks the transport:**
|
|
93
|
+
|
|
94
|
+
| Model id | Transport | Usage it can report |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `gemini-*` (e.g. `gemini-3.1-flash-lite-image`) | `ai.models.generateContent` with `responseModalities` including `"IMAGE"` | whatever `usageMetadata` Google attaches — price with `{ input, output }` |
|
|
97
|
+
| anything else (`imagen-*`, unknown ids) | `ai.models.generateImages` (Imagen) | none, always zero — price with `{ perImage }` |
|
|
98
|
+
|
|
99
|
+
A `gemini-` id sent to `generateImages` comes back `404 … is not supported for predict` (observed verbatim from Google) — `generateImages` routes to `{model}:predict`. `generateContent` is the SDK's own named replacement: its runtime deprecation notice for `generateImages` says *"Please use the generateContent method with image models instead"*. Routing on the id is what lets both families go through one factory. A leading `models/` resource prefix is tolerated.
|
|
100
|
+
|
|
101
|
+
This is **routing, not validation** — no id is refused locally. An id matching neither family takes the `generateImages` route (the pre-existing one) and fails at Google as a typed provider error, not with a local throw.
|
|
102
|
+
|
|
103
|
+
> ⚠ **No automated test here calls Google**, so the evidence is two-tier. **Measured in this repo:** a `gemini-*` image id that 404s on `predict` reached the model on `generateContent` and returned a **quota error (HTTP 429)** — the endpoint accepts the id. **Reported by the maintainer:** once billing was enabled on the project, an image came back end-to-end through a locally linked build. What remains unknown is **token reporting** — no `usageMetadata` from a successful image call has been seen yet.
|
|
104
|
+
|
|
105
|
+
### Gemini (`generateContent`)
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const gemini = google.image({
|
|
109
|
+
name: "gemini-3.1-flash-lite-image",
|
|
110
|
+
pricing: { input: 0.3, output: 30 }, // token-metered
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const { data, error, usage } = await ai.image({
|
|
114
|
+
model: gemini,
|
|
115
|
+
prompt: "a red bicycle on a white background",
|
|
116
|
+
aspectRatio: "16:9", // → config.imageConfig.aspectRatio
|
|
117
|
+
options: { imageSize: "2K", personGeneration: "ALLOW_ADULT" }, // → imageConfig passthroughs
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- Requests `responseModalities: ["TEXT", "IMAGE"]` by default; pass `options.responseModalities` to replace the list verbatim. Which pairing a given model requires is not established here.
|
|
122
|
+
- Inline image parts that **do** come back (`inlineData.data` + `mimeType`) map to the **same** `GeneratedImage` shape Imagen produces — `{ type: "base64", base64, mediaType }`, defaulting to `image/png` when the part declares no mime type. Callers see one shape regardless of transport.
|
|
123
|
+
- **Token usage is passed through, not zeroed.** Any `usageMetadata` Google attaches is mapped by the same rules as `model.complete()` (`promptTokenCount` → `input`, `candidatesTokenCount` → `output`, `cachedContentTokenCount` → `cachedTokens`, `thoughtsTokenCount` → `reasoningTokens`); an absent block collapses to `{ 0, 0, 0 }`. That is the difference from Imagen, which is always zero. Whether these models in fact report tokens is unconfirmed — price with `{ input, output }` and check the first live `usage`.
|
|
124
|
+
- **A response with no image part is never a silent empty success:**
|
|
125
|
+
- `promptFeedback.blockReason`, or a safety/policy `finishReason` (`SAFETY`, `IMAGE_SAFETY`, `PROHIBITED_CONTENT`, `IMAGE_PROHIBITED_CONTENT`, `RECITATION`, `IMAGE_RECITATION`, `BLOCKLIST`, `SPII`) → `ContentFilterError` carrying the reason.
|
|
126
|
+
- text only (the model answered instead of drawing) → `ProviderError` **quoting the text** it returned.
|
|
127
|
+
- nothing usable → `ProviderError` naming the part count and finish reason (e.g. `NO_IMAGE`).
|
|
128
|
+
- `count`, `negativePrompt`, and `format` are **not** forwarded on this path — `GenerateContentConfig` / `ImageConfig` expose no equivalent (`ImageConfig.outputMimeType` is documented "not supported in Gemini API"). Fold those intentions into the prompt. Every inline image part the model does return is mapped, so a multi-image answer still comes back whole.
|
|
129
|
+
|
|
130
|
+
### Imagen (`generateImages`)
|
|
93
131
|
|
|
94
132
|
```ts
|
|
95
133
|
const imagen = google.image({ name: "imagen-4.0-generate-001", pricing: { perImage: 0.04 } });
|
|
@@ -103,9 +141,15 @@ const { data, error } = await ai.image({
|
|
|
103
141
|
});
|
|
104
142
|
```
|
|
105
143
|
|
|
106
|
-
- Imagen is **per-image-metered** (price with `{ perImage }`) and returns base64 bytes — no hosted URL,
|
|
107
|
-
- When every candidate is safety-filtered, the run surfaces a typed `ContentFilterError` on `result.error`.
|
|
108
|
-
-
|
|
144
|
+
- Imagen is **per-image-metered** (price with `{ perImage }`) and returns base64 bytes — no hosted URL, and `usage` is an honest `{ input: 0, output: 0, total: 0 }`.
|
|
145
|
+
- When every candidate is safety-filtered (`raiFilteredReason`), the run surfaces a typed `ContentFilterError` on `result.error`.
|
|
146
|
+
- ⏳ **This path is on a clock.** `@google/genai` warns at runtime: *"The generateImages method is deprecated and will be removed in the next major release (not before Jan. 1 2027). Please use the generateContent method with image models instead."* This package depends on `@google/genai` `^2.17.1`, which does emit that warning. Nothing breaks today, but new work should prefer a `gemini-*` id.
|
|
147
|
+
|
|
148
|
+
### Not implemented: the `interactions` API
|
|
149
|
+
|
|
150
|
+
`@google/genai` ≥ 2.17 adds `ai.interactions.create({ model, input, response_format: { type: "image", … } })`, whose images arrive as `interaction.output_image.data` and whose usage is a different snake_case shape (`total_input_tokens` …). It is reachable from the `^2.17.1` this package depends on, but the adapter does **not** use it: it would need its own usage mapper and error surface, and its request type already marks `response_modalities` / `response_mime_type` deprecated. If it is added later it will be an **opt-in config flag**, never id routing.
|
|
151
|
+
|
|
152
|
+
The verb surface lives in [`@warlock.js/ai/generate-images/SKILL.md`](@warlock.js/ai/generate-images/SKILL.md).
|
|
109
153
|
|
|
110
154
|
## Streaming
|
|
111
155
|
|
package/llms.txt
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [setup-google](@warlock.js/ai-google/setup-google/SKILL.md): 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. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for
|
|
9
|
+
- [setup-google](@warlock.js/ai-google/setup-google/SKILL.md): 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. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for image generation via ai.image — the id picks the transport: gemini-* → generateContent + responseModalities IMAGE (usage passed through; generateImages is deprecated by Google), everything else → generateImages (Imagen, per-image, zero usage) — extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `gemini image`, `nano banana`, `responseModalities`, `imageConfig`, `ai.image`, `inlineData`, `pdf input`, `audio input`, `thoughtSignature`, `responseJsonSchema`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `promptCaching`, `cacheControl`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls", "gemini 2.5 thinking budget", "gemini cached content cost", "generate images with imagen", "generate an image with gemini", "gemini image model 404 not supported for predict", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — `@warlock.js/ai/generate-images/SKILL.md`; 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`.
|
package/package.json
CHANGED
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"type": "git",
|
|
14
14
|
"url": "https://github.com/warlockjs/ai-google"
|
|
15
15
|
},
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"@google/genai": "^2.4.0",
|
|
18
|
-
"@warlock.js/logger": "4.14.0"
|
|
19
|
-
},
|
|
20
16
|
"peerDependencies": {
|
|
21
|
-
"@warlock.js/ai": "4.
|
|
17
|
+
"@warlock.js/ai": "4.15.0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@google/genai": "^2.17.1",
|
|
21
|
+
"@warlock.js/logger": "4.15.0"
|
|
22
22
|
},
|
|
23
|
-
"version": "4.
|
|
23
|
+
"version": "4.15.0",
|
|
24
24
|
"main": "./cjs/index.cjs",
|
|
25
25
|
"module": "./esm/index.mjs",
|
|
26
26
|
"types": "./esm/index.d.mts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: setup-google
|
|
3
|
-
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. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for
|
|
3
|
+
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. .model({name, vision?, reasoning?, audio?, pdf?}) with cost-truth capabilities (PDF + audio input map to Gemini inlineData), .image({name, pricing?}) for image generation via ai.image — the id picks the transport: gemini-* → generateContent + responseModalities IMAGE (usage passed through; generateImages is deprecated by Google), everything else → generateImages (Imagen, per-image, zero usage) — extended thinking via options.reasoning → thinkingConfig.thinkingBudget, usage reasoningTokens (thoughtsTokenCount) / cachedTokens (cachedContentTokenCount). Triggers: `GoogleSDK`, `google.model`, `google.embedder`, `google.image`, `imagen`, `generateImages`, `gemini image`, `nano banana`, `responseModalities`, `imageConfig`, `ai.image`, `inlineData`, `pdf input`, `audio input`, `thoughtSignature`, `responseJsonSchema`, `vertexai`, `reasoning`, `thinkingConfig`, `thinkingBudget`, `thoughtsTokenCount`, `reasoningTokens`, `cachedTokens`, `promptCaching`, `cacheControl`; "use gemini", "wire Vertex AI", "gemini embeddings", "gemini thinking tool calls", "gemini 2.5 thinking budget", "gemini cached content cost", "generate images with imagen", "generate an image with gemini", "gemini image model 404 not supported for predict", "send a pdf / audio to gemini"; import `import { GoogleSDK } from "@warlock.js/ai-google"`. Skip: the ai.image verb surface — `@warlock.js/ai/generate-images/SKILL.md`; 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`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `@warlock.js/ai-google`
|
|
@@ -79,9 +79,47 @@ Gemini's multimodal input is **media-agnostic** — every binary modality maps t
|
|
|
79
79
|
|
|
80
80
|
PDF and audio reach the wire only when the model declares the matching capability (inferred for the multimodal Gemini families above) — so capability ≡ behavior.
|
|
81
81
|
|
|
82
|
-
## Image generation
|
|
82
|
+
## Image generation — two transports
|
|
83
83
|
|
|
84
|
-
`google.image({ name })` returns an `ImageModelContract`
|
|
84
|
+
`google.image({ name })` returns an `ImageModelContract` for the `ai.image()` verb. **The model id picks the transport:**
|
|
85
|
+
|
|
86
|
+
| Model id | Transport | Usage it can report |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| `gemini-*` (e.g. `gemini-3.1-flash-lite-image`) | `ai.models.generateContent` with `responseModalities` including `"IMAGE"` | whatever `usageMetadata` Google attaches — price with `{ input, output }` |
|
|
89
|
+
| anything else (`imagen-*`, unknown ids) | `ai.models.generateImages` (Imagen) | none, always zero — price with `{ perImage }` |
|
|
90
|
+
|
|
91
|
+
A `gemini-` id sent to `generateImages` comes back `404 … is not supported for predict` (observed verbatim from Google) — `generateImages` routes to `{model}:predict`. `generateContent` is the SDK's own named replacement: its runtime deprecation notice for `generateImages` says *"Please use the generateContent method with image models instead"*. Routing on the id is what lets both families go through one factory. A leading `models/` resource prefix is tolerated.
|
|
92
|
+
|
|
93
|
+
This is **routing, not validation** — no id is refused locally. An id matching neither family takes the `generateImages` route (the pre-existing one) and fails at Google as a typed provider error, not with a local throw.
|
|
94
|
+
|
|
95
|
+
> ⚠ **No automated test here calls Google**, so the evidence is two-tier. **Measured in this repo:** a `gemini-*` image id that 404s on `predict` reached the model on `generateContent` and returned a **quota error (HTTP 429)** — the endpoint accepts the id. **Reported by the maintainer:** once billing was enabled on the project, an image came back end-to-end through a locally linked build. What remains unknown is **token reporting** — no `usageMetadata` from a successful image call has been seen yet.
|
|
96
|
+
|
|
97
|
+
### Gemini (`generateContent`)
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const gemini = google.image({
|
|
101
|
+
name: "gemini-3.1-flash-lite-image",
|
|
102
|
+
pricing: { input: 0.3, output: 30 }, // token-metered
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const { data, error, usage } = await ai.image({
|
|
106
|
+
model: gemini,
|
|
107
|
+
prompt: "a red bicycle on a white background",
|
|
108
|
+
aspectRatio: "16:9", // → config.imageConfig.aspectRatio
|
|
109
|
+
options: { imageSize: "2K", personGeneration: "ALLOW_ADULT" }, // → imageConfig passthroughs
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- Requests `responseModalities: ["TEXT", "IMAGE"]` by default; pass `options.responseModalities` to replace the list verbatim. Which pairing a given model requires is not established here.
|
|
114
|
+
- Inline image parts that **do** come back (`inlineData.data` + `mimeType`) map to the **same** `GeneratedImage` shape Imagen produces — `{ type: "base64", base64, mediaType }`, defaulting to `image/png` when the part declares no mime type. Callers see one shape regardless of transport.
|
|
115
|
+
- **Token usage is passed through, not zeroed.** Any `usageMetadata` Google attaches is mapped by the same rules as `model.complete()` (`promptTokenCount` → `input`, `candidatesTokenCount` → `output`, `cachedContentTokenCount` → `cachedTokens`, `thoughtsTokenCount` → `reasoningTokens`); an absent block collapses to `{ 0, 0, 0 }`. That is the difference from Imagen, which is always zero. Whether these models in fact report tokens is unconfirmed — price with `{ input, output }` and check the first live `usage`.
|
|
116
|
+
- **A response with no image part is never a silent empty success:**
|
|
117
|
+
- `promptFeedback.blockReason`, or a safety/policy `finishReason` (`SAFETY`, `IMAGE_SAFETY`, `PROHIBITED_CONTENT`, `IMAGE_PROHIBITED_CONTENT`, `RECITATION`, `IMAGE_RECITATION`, `BLOCKLIST`, `SPII`) → `ContentFilterError` carrying the reason.
|
|
118
|
+
- text only (the model answered instead of drawing) → `ProviderError` **quoting the text** it returned.
|
|
119
|
+
- nothing usable → `ProviderError` naming the part count and finish reason (e.g. `NO_IMAGE`).
|
|
120
|
+
- `count`, `negativePrompt`, and `format` are **not** forwarded on this path — `GenerateContentConfig` / `ImageConfig` expose no equivalent (`ImageConfig.outputMimeType` is documented "not supported in Gemini API"). Fold those intentions into the prompt. Every inline image part the model does return is mapped, so a multi-image answer still comes back whole.
|
|
121
|
+
|
|
122
|
+
### Imagen (`generateImages`)
|
|
85
123
|
|
|
86
124
|
```ts
|
|
87
125
|
const imagen = google.image({ name: "imagen-4.0-generate-001", pricing: { perImage: 0.04 } });
|
|
@@ -95,9 +133,15 @@ const { data, error } = await ai.image({
|
|
|
95
133
|
});
|
|
96
134
|
```
|
|
97
135
|
|
|
98
|
-
- Imagen is **per-image-metered** (price with `{ perImage }`) and returns base64 bytes — no hosted URL,
|
|
99
|
-
- When every candidate is safety-filtered, the run surfaces a typed `ContentFilterError` on `result.error`.
|
|
100
|
-
-
|
|
136
|
+
- Imagen is **per-image-metered** (price with `{ perImage }`) and returns base64 bytes — no hosted URL, and `usage` is an honest `{ input: 0, output: 0, total: 0 }`.
|
|
137
|
+
- When every candidate is safety-filtered (`raiFilteredReason`), the run surfaces a typed `ContentFilterError` on `result.error`.
|
|
138
|
+
- ⏳ **This path is on a clock.** `@google/genai` warns at runtime: *"The generateImages method is deprecated and will be removed in the next major release (not before Jan. 1 2027). Please use the generateContent method with image models instead."* This package depends on `@google/genai` `^2.17.1`, which does emit that warning. Nothing breaks today, but new work should prefer a `gemini-*` id.
|
|
139
|
+
|
|
140
|
+
### Not implemented: the `interactions` API
|
|
141
|
+
|
|
142
|
+
`@google/genai` ≥ 2.17 adds `ai.interactions.create({ model, input, response_format: { type: "image", … } })`, whose images arrive as `interaction.output_image.data` and whose usage is a different snake_case shape (`total_input_tokens` …). It is reachable from the `^2.17.1` this package depends on, but the adapter does **not** use it: it would need its own usage mapper and error surface, and its request type already marks `response_modalities` / `response_mime_type` deprecated. If it is added later it will be an **opt-in config flag**, never id routing.
|
|
143
|
+
|
|
144
|
+
The verb surface lives in [`@warlock.js/ai/generate-images/SKILL.md`](@warlock.js/ai/generate-images/SKILL.md).
|
|
101
145
|
|
|
102
146
|
## Streaming
|
|
103
147
|
|