@noetaris/harness-google
v0.6.0
Published
Google Gemini adapter for @noetaris/harness
Readme
@noetaris/harness-google
Google Gemini adapter for @noetaris/harness.
Overview
@noetaris/harness-google provides a Gemini class that implements the LLM and ObserverAware interfaces from @noetaris/harness. It handles translation between the harness message format and the Google Generative AI SDK (generateContent) format, and emits telemetry events (token usage, model ID) through an attached Observer.
Installation
pnpm add @noetaris/harness-googlePeer dependencies:
pnpm add @noetaris/harness @noetaris/harness-typesRequires Node.js ≥ 22.
Usage
import { Gemini } from '@noetaris/harness-google'
// The model ID is the required first argument; options are optional.
const llm = new Gemini('gemini-2.0-flash', {
apiKey: process.env.GEMINI_API_KEY, // defaults to the GEMINI_API_KEY env var
})
// Wire into a harness provider slot
h.provide('model', runtime())
const agent = createAgent(h, { prompts: { system: '...' } })
const run = agent.run(initialState, { model: llm })API
Gemini
new Gemini(model: string, options?: GeminiOptions)Implements LLM and ObserverAware. GeminiOptions accepts apiKey (defaults
to the GEMINI_API_KEY env var) and the generation parameters temperature,
maxTokens, topP, topK, and thinkingConfig (extended-reasoning budget for
supported models).
invoke(messages, options?)— translates harnessMessage[]andTool[]to Google Generative AI SDK format, callsgenerateContent(), and maps the response back to anLLMResponse(including function-call extraction from candidates and a requiredusage: { inputTokens, outputTokens, contextWindowSize? }field).bindObserver(observer)— attaches anObserver. Eachinvokeemits an"llm.request"event ({ modelId, providerName: 'google' }) before the call and an"llm.response"event ({ tokens: { input, output }, modelId, stopReason, providerName, contextWindowSize? }) from the responseusageMetadataafter it.setStepContext(ctx)— sets theStepContextattached to emitted events; called by the harness before each step.
Image input
Image input needs @noetaris/harness-types 0.5.0 or later.
A user message can carry images next to text. Pass blocks instead of a string:
import { readFileSync } from 'node:fs'
import { Gemini } from '@noetaris/harness-google'
const llm = new Gemini('gemini-2.0-flash')
const response = await llm.invoke([
{
role: 'user',
content: [
{ type: 'text', text: 'What is in this picture?' },
{ type: 'image', data: readFileSync('photo.png').toString('base64'), mediaType: 'image/png' },
],
},
])- No type check. The adapter sends any
mediaTypeasinlineData.mimeTypeand throws nothing. The provider decides. Gemini's image guide lists PNG, JPEG, WEBP, HEIC and HEIF. datais raw base64, passed through unchanged. Do not add adata:prefix.- Size: Gemini's guide says inline image data limits the total request size (text, system instructions and inline bytes) to 20 MB.
- API shape: the adapter calls
generateContent, which takes aninlineDatapart (mimeType,data). Gemini's image guide shows base64 in the Interactions API shape, so do not copy its snippet. - String content is sent as before. An array becomes one part per block, in the same order.
- The adapter does not check that the model can read images. The provider decides.
- The adapter imports
toBlocksfrom@noetaris/harness-typesat runtime, so that package must be installed (it is listed above as a peer dependency). - Images kept in session state are stored again on every run. See Large media in session state.
Known limitation (fork/join branches):
setStepContextstores the givenStepContextin an instance field andinvoke()reads it back when emittingonEvent. This is safe when one step runs at a time, but not when a singleGeminiinstance is shared across concurrent fork branches (the normal setup — one instance provided once viah.provide('llm', ...)): concurrent branches can race on that stored field, and telemetry may be attributed to the wrong branch/step. Not fixed in this release — a correct fix means passingStepContextthrough the call instead of storing it, which is a larger, separate change. If you use@noetaris/harness's fork/join feature with this adapter, treat per-step LLM telemetry as unreliable for concurrently-running branches.
MockGemini
A deterministic test double for use in tests and demos without a real API key.
Related Packages
@noetaris/harness— core execution engine@noetaris/harness-types— shared LLM type contract@noetaris/harness-anthropic— Anthropic Claude adapter@noetaris/harness-openai— OpenAI adapter@noetaris/harness-otel— OpenTelemetry observer bridge
License
MIT
