@intentqa/llm-google
v2.0.0
Published
Google Gemini planner adapter for the intent-driven QA SDK. Bring your own API key.
Readme
@intentqa/llm-google
Google Gemini planner adapter for intentqa.
pnpm add -D @intentqa/llm-google
export GOOGLE_API_KEY=... # GEMINI_API_KEY works too
npx intentqa generate --intent "a premium user can apply a coupon"Bring your own key, always. This package never proxies through a key the project owns: a testing tool that quietly spends someone else's money is a tool nobody can adopt at work. Plans are cached on disk against the intent, the catalog hash and the prompt version, so unchanged intent against an unchanged vocabulary costs nothing to re-run.
No dependencies beyond intentqa itself. This adapter posts to the Generative Language
API's :generateContent with the global fetch and hand-rolled JSON rather than taking
@google/genai as a dependency — the same call @intentqa/mcp makes with JSON-RPC. One
request shape does not justify putting a vendor SDK, and its transitive tree, into
everyone's lockfile.
Defaults to gemini-3-pro against https://generativelanguage.googleapis.com/v1beta.
Every default is an option:
import { googlePlanner } from "@intentqa/llm-google";
export const planner = googlePlanner({ model: "gemini-3-flash", maxTokens: 24000 });The schema is sanitized, and that is lossy
Gemini's responseSchema accepts only a subset of JSON Schema and rejects the whole
request when it meets a keyword outside that subset. The plan schema is generated from Zod
by zod-to-json-schema in intentqa, so toGeminiSchema narrows it on the way
out: it drops additionalProperties, $schema, definitions, $ref, const and
default, and folds anyOf: [X, { type: "null" }] into { ...X, nullable: true }.
Two honest consequences. Dropping additionalProperties: false means Gemini is no longer
told to reject extra keys, so a stray field reaches the compiler instead of the provider.
And this is only safe because intentqa emits the schema with $refStrategy:
"none" — the schema is fully inlined, so there is no $ref whose target matters. If that
ever changes, stripping $ref would silently send Gemini a schema with holes in it, and
this function has to change with it.
The reply is re-validated against PlanWireSchema regardless, so a constraint lost here
costs a less precise model error, not a wrong plan. toGeminiSchema is exported if you
want to see what your catalog turns into.
There is a second field, responseJsonSchema, that takes real JSON Schema — anyOf,
$ref and all — and would need none of this. It is mutually exclusive with
responseSchema, and this adapter uses the older field because it is the one every current
model supports. When that stops being true, this whole section should disappear.
The prompt and the plan schema live in intentqa, not here, so swapping providers
cannot silently change what the model is asked to do — and a cross-provider comparison
stays meaningful.
The model gets an output schema, no tools, and no way to emit code. The worst it can do is pick the wrong step from the catalog, which the compiler catches and the reviewer sees.
